Upload
gerrit-garbereder
View
243
Download
2
Embed Size (px)
DESCRIPTION
Ein Vortrag über Filehandling mit PHP und CakePHP
Citation preview
Gerrit Garbereder 16.03.2012
Filehandling & MVCFilehandling & MVCmit PHPmit PHP
vonvonGerrit GarberederGerrit Garbereder
[email protected]@Garbereder.de
Gerrit Garbereder
2
2/6616.03.2012
22
Übersicht
● Filehandling● Übersicht● Dateien auflisten● Ordner wechseln● Dateien hochladen● Dateien kopieren / verschieben● Ordner anlegen● Ordner kopieren / verschieben● Dateien / Ordner löschen● Dateien bearbeiten● Fallstricke
● MVC● Motivation● Design Pattern● MVC in PHP● CakePHP
Gerrit Garbereder
3
3/6616.03.2012
33
Filehandling
„Refers to working with files that are stored on the hard
disk.“Quelle (03.02.12): http://www.pcmag.com/encyclopedia_term/0,2542,t=file+handling&i=43163,00.asp
Gerrit Garbereder
4
4/6616.03.2012
44
Filehandling
● PHP bietet umfangreiche API zur Dateiverarbeitung (~50 Funktionen)● http://de3.php.net/manual/de/ref.filesystem.php
● Manche Befehle funktionieren nur auf Unix/Linux Systemen (chmod)● Vorstellung der API anhand eines simplen webbasierten Filebrowsers● ACHTUNG! Nie, Benutzereingaben ungeprüft übernehmen:
$dir = „/home/“;delete($dir.$_GET[„fname“]);
● ,weil
$_GET[„fname“] := „../etc/passwd“● eine gültige Pfadangabe ist!
Gerrit Garbereder
5
5/6616.03.2012
55
Filehandling
● Grundsätzliche Funktionen● file_exists ($name) // true, wenn Datei oder Ordner existiert● is_file($name) // true, wenn es eine reguläre Datei ist● is_dir($name) // true, wenn es ein Ordner ist● getcwd() // gibt den aktuellen Pfad an● chdir($name) // wechselt in den angegebenen Pfad
Gerrit Garbereder 16.03.2012
FilebrowserFilebrowser
Gerrit Garbereder
7
7/6616.03.2012
77
Filehandling
● Grundlegende Funktionalitäten zur Dateimanipulation● Übergabe der Parameter mittels GET ( debugging / nachvollziehen )● $_GET[„action“] entscheided darüber welche Aktion ausgeführt
wird● Weitere notwendige Parameter werden aus verstecken Feldern
übergeben● Verarbeitung der Parameter vor der Anzeige ( = Dateiliste )
=> Anzeige stets aktuell● Fehler sollen abgefangen werden
Gerrit Garbereder
8
8/6616.03.2012
88
Filehandling
Gerrit Garbereder
9
9/6616.03.2012
99
Filehandling
Gerrit Garbereder
10
10/6616.03.2012
1010
Filehandling
Gerrit Garbereder
11
11/6616.03.2012
1111
Filehandling
Gerrit Garbereder 16.03.2012
Dateien auflistenDateien auflisten
Gerrit Garbereder
13
13/6616.03.2012
1313
Filehandling
● dir($name) // gibt eine Instanz der Directory Klasse zurück● path● handle● read()● rewind()● Close()
● Pseudo Objektorientiert!● Alternativ $h = opendir(), readdir($h), rewinddir($h), closedir($h)
Gerrit Garbereder
14
14/6616.03.2012
1414
Filehandling
$d = dir(„/home/“);while(($file = $d->read()) !== FALSE) echo $file;
oder Iterator (objektorientiert)
$it = new FilesystemIterator(„/home/“);while($it->valid()){ echo $it->getFilename(); $it->next();}
Gerrit Garbereder
15
15/6616.03.2012
1515
Filehandling
● Es wird keine Unterscheidung zwischen Dateien und Ordnern vorgenommen.
● Mit is_dir / is_file, kann der Entwickler sortieren / filtern
● . / .. sind Ordner
● Für den Filebrowser verwenden wir zunächst folgenden Code:function printFileList($pDir = „.“){ $dir = dir($pDir); while(($current = $dir->read()) !== FALSE) if(is_dir($current) && $current != „.“) $dirs[] = $current; elseif(is_file($current)) $files[] = $current; @sort($dirs); @sort($files); if($dirs) foreach($dirs as $d) echo $d . „<br>“; // Hier wäre das Decorator-Pattern sinnvoll! if($files) foreach($files as $f) echo $f .“<br>“;}
Gerrit Garbereder 16.03.2012
Ordner wechselnOrdner wechseln
Gerrit Garbereder
17
17/6616.03.2012
1717
Filehandling
● Eine PHP Datei befindet sich in einem Ordner, von diesem Ordner aus werden relative Pfade berechnet
● chdir() wechselt diesen Ordner● HTTP ist aber zustandslos● Abhilfe:
● Aktueller Ordner wird in der Session gespeichert● Der Benutzer, darf nicht zu weit nach „oben“ navigieren
● Lege oberstes Verzeichnis fest:define(BASEDIR,“/home/“);
● Ständiger Abgleich: aktueller Order BASEDIR↔● GET als HTTP Protokolltyp damit per Linkklick navigiert werden kann
Gerrit Garbereder
18
18/6616.03.2012
1818
Filehandling
● Bei jeder Aktion wird geprüft, ob das Verzeichnis in Ordnung ist!switch(@$_GET[„action“]){ [...] case „navto“: if(@$_GET[„name“] && chdir(@$_GET[„name“])) $_SESSION[„dir“] = getcwd(); else die(„Error!“); break; [...]}
if(!( (realpath($_SESSION[„dir“]) == realpath(„.“)) && (stripos(realpath($_SESSION[„dir“]),BASEDIR)===0) )){ die(„403 Forbidden“);}
Gerrit Garbereder 16.03.2012
Datei hochladenDatei hochladen
Gerrit Garbereder
20
20/6616.03.2012
2020
Filehandling
● Formularkodierung muss multipart/form-data sein● Formularfelder für den Dateiupload sind vom Typ file● Seit HTML 5 Mehrfachauswahl beim Upload möglich
● Name des Feldes muss sein Array sein z.B. files[]● PHP stellt die hochgeladenen Dateien in dem Array $_FILES bereit
● Achtung! Nur bei HTTP POST-Methode!● Jede Datei in dem Array muss einzeln behandelt werden● Verschieben der Datei aus dem Temp Ordner in das aktuelle
Verzeichnis
Gerrit Garbereder
21
21/6616.03.2012
2121
Filehandling
Array ( [files] => Array ( [name] => Array( [0] => ue01_a12.txt [1] => ue01_a13.txt ) [type] => Array ( [0] => text/plain [1] => text/plain ) [tmp_name] => Array ( [0] => /is/htdocs/user_tmp/wp1114652_7XBB85T1UF/php4AU46J [1] => /is/htdocs/user_tmp/wp1114652_7XBB85T1UF/phpV9Ab16 ) [error] => Array ( [0] => 0 [1] => 0 ) [size] => Array ( [0] => 985 [1] => 564 ) ))
Gerrit Garbereder
22
22/6616.03.2012
2222
Filehandling
if(@$_POST[„action“]==“upload“) for($i=0;$i<count($_FILES[„files“][„error“]);++$i) if($_FILES[„files“][„error“][$i]==0 && move_uploaded_file( $_FILES[„files“][„tmp_name“][$i], $_FILES[„files“][„name“][$i] ) ) echo $_FILES[„files“][„name“][$i].“ uploaded“;
Gerrit Garbereder 16.03.2012
Datei kopierenDatei kopieren//
verschiebenverschieben
Gerrit Garbereder
24
24/6616.03.2012
2424
Filehandling
● 1. Schritt: Datei auswählen● 2. Schritt: Datei merken● 3. Schritt: Datei kopieren / verschieben● Quelldatei wird in der Session gespeichert, damit diese nicht
„mitgeschleppt“ werden muss● $_SESSION[„copy“] = $_SESSION[„dir“].“/“.$_GET[„name“];● $_SESSION[„cut“] = null;
● Falls eine gleichnamige Datei schon vorhanden ist, wird diese ohne Warnung überschrieben
● Ordner müssen gesondert behandelt werden! (Rekursion)
Gerrit Garbereder
25
25/6616.03.2012
2525
Filehandling
function fullcopy($source,$target){ $pi = pathinfo($source); if(is_dir($source)){ [...] } else{ if(is_dir($target)) $target .= „/“.$pi[„basename“]; if(stripos( realpath($source),BASEDIR) ===0 ) return copy($source,$target); }}● Achtung! pathinfo($name)[„basename“] != basename($name)● Verschieben analog
Gerrit Garbereder 16.03.2012
Ordner anlegenOrdner anlegen
Gerrit Garbereder
27
27/6616.03.2012
2727
Filehandling
● mkdir($name) legt einen neuen Ordner an● Über Parameter können außerdem
● die Zugriffsrechte vergeben werden. Default: 0777● verschachtelte Ordner angelegt werden. Default: FALSE● seit PHP 5 unterstützt mkdir auch Kontexte
● Auch beim anlegen muss überprüft werden ob der Pfad valide ist● z.B. preg_match('/^[ a-z0-9_-]+$/i',$_GET[„name“]);
● Lieber zu streng sein als wild auf dem Server herum schreiben● Hier darf der Ordner nur aus Zahlen, Buchstaben, Space, _ und -
bestehen
Gerrit Garbereder 16.03.2012
Ordner kopierenOrdner kopieren//
verschiebenverschieben
Gerrit Garbereder
29
29/6616.03.2012
2929
Filehandling
● Nur leere Ordner können kopiert oder verschoben werden● Ansatz:
● Tiefensuche bis zur tiefsten Ebene● Auf dem „Weg runter“ Unterordner im Zielordner anlegen● Auf dem „Weg rauf“ Dateien kopieren / verschieben
● Wenn Ordner verschoben werden, wird der alte leere Ordner gelöscht● Funktion arbeitet rekursiv = selbstaufrufend● Auch hier Dateipfade auf Korrektheit prüfen!● Erweiterung der bisher genutzten Methoden
Gerrit Garbereder
30
30/6616.03.2012
3030
Filehandling
function fullcopy($source,$target){ $pi = pathinfo($source); if(is_dir($source)){ $target = $target.“/“.$pi[„basename“]; if(!(is_dir($target)||mkdir($target))) die(„Error!“);
$d = dir($source); while(($entry = $d->read())!==FALSE){ if($entry==“.“||$entry==“..“) continue; $e = $source.“/“.$entry; if(is_dir($e)) fullcopy($e,$target.“/“.$entry); //REKURSION! else if(stripos(realpath($e),BASEDIR)===0) copy($e,$target.“/“.$entry); } $d->close() }else{ […] }}
Gerrit Garbereder 16.03.2012
Dateien / OrdnerDateien / Ordnerlöschenlöschen
Gerrit Garbereder
32
32/6616.03.2012
3232
Filehandling
● Das Löschen verhält sich analog zum kopieren von Daten● 1. Schritt Tiefensuche● 2. Schritt Alle Dateien in einem Ordner löschen● 3. Schritt Ordner selbst löschen
● Lösungansatz zur Vermeindung von doppelten Code● Eine Funktion zur Rekursiven Abarbeitung von Ordnern● Abstrakte Klasse zur Verarbeitung von Dateien / Ordnern
abstract class AbstractDirectoryRekursionHandler{ abstract function onFileEnter(); abstract function onFileLeave(); abstract function onDirEnter(); abstract function onDirLeave(); abstract function onBegin(); abstract function onEnd(); }
● Vgl. Strategie Pattern
Gerrit Garbereder 16.03.2012
Dateien bearbeitenDateien bearbeiten
Gerrit Garbereder
34
34/6616.03.2012
3434
Filehandling
● Textdateien können als String gelesen werden● file_get_contents()
● Methoden für ini und csv Dateien werden bereitgestellt● Zeichen- /zeilenweises Einlesen ist auch möglich● Meist ist mod_gd zur Bildverarbeitung installiert● Weitere Module und Bibliotheken existieren für diverse Dateitypen● Achtung! Wird eine html Datei als String gelesen und dieser wieder
ausgegeben werden die HTML Tags ausgewertet.● Abhilfe schafft htmlentities()● Ist der gesamte Sting maskiert worden kann er vor dem schreiben mittels html_entity_decode() wieder dekodiert werden.
Gerrit Garbereder
35
35/6616.03.2012
3535
Filehandling
● In der Regel muss eine Datei zunächst geöffnet werden● $handle = fopen($name,$mode) öffnet die Datei, der Modus
kann dabei lesend und/oder schreibend sein und es kann bestimmt werden, ob am Anfang oder am Ende der Datei geschrieben / gelesen werden soll.
● fclose($handle) schließt die Datei wieder und gibt sie zur Bearbeitung für andere frei
● file_get_contents($name) kapselt das öffnen und schließen der Datei vor dem Nutzer
● Zur Übergabe des Dateiinhaltes sollte POST verwendet werden, da eine URL oft auf 2048 Zeichen beschränkt ist
Gerrit Garbereder
36
36/6616.03.2012
3636
Filehandling
● Für fast jede Lesefunktion für Dateien gibt es eine passende Schreibfunktion
● file_put_contents($name,$content)● fwrite($handle,$data)● fputs($handle,$string)● fputcsv($handle,$contentArray)
Gerrit Garbereder 16.03.2012
FallstrickeFallstricke
Gerrit Garbereder
38
38/6616.03.2012
3838
Filehandling
● Generell sind Benutzereingaben sehr kritisch, sowohl bei Dateipfaden als auch beim Dateiinhalt. So könnte bei falscher Einstellung der Schreibrechte eine Konfigurationsdatei überschrieben werden
=> Nur die Order schreibbar machen bei denen es wirklich notwendig ist. Meist ex. ein extra Uploadordner
● Auch sollte vor dem lesen / schreiben einer Datei geprüft werden, ob die Rechte existieren oder im Fehlerfall entsprechend reagieren
● Bei Dateiuploads muss man auf den korrekten mime-type achten sowie Fehler abfangen● Fehlermeldungen sollten unterdrückt werden, damit keine sicherheitsrelevanten Daten
sichtbar werden● Dateipfade in der URL sollten mit url_(en|de)code() maskiert werden● Wenn Dateien via GET gelöscht werden können, löscht ein Crawler u.U. Daten
Gerrit Garbereder 16.03.2012
Fragen?Fragen?
Gerrit Garbereder 16.03.2012
http://www.gettyicons.com/free-icon/112/office-space-icon-set/free-cup-coffee-icon-png/
Gerrit Garbereder 16.03.2012
MVCMVC
Gerrit Garbereder
42
42/6616.03.2012
4242
MVC
● Ausgangssituation● Mehrere Datenquellen● Unterschiedliche Zugriffsrechte● Mehrere Anzeige Systeme
● Naiver Ansatz:● Für jede Quellen-Rechte-Display Kombination eine Klasse schreiben● Nachteil: Exponentiell wachsende Anzahl an Klassen
● Besserer Ansatz:● Für jede Quelle, Recht, Display je eine Klasse● Kommunikationsschnittstelle zwischen den Klassen schaffen● Vorteil: Linear wachsende Anzahl an Klassen
Gerrit Garbereder
43
43/6616.03.2012
4343
MVC
Gerrit Garbereder
44
44/6616.03.2012
4444
MVC
● MVC trennt die einzelnen Komponenten, sodass der Implementierungsaufwand reduziert wird
● Beispiel: MVC mit ● Models: SQLModel, FileModel● Views: DesktopView, MobileView● Controller: RWController, RController
● => 8 Kombinationen● +1 Klasse: 12 Kombinationen● +2 Klassen: 16 bzw. 18 Kombinationen● Je mehr Klassen zur Auswahl stehen, desto besser skaliert MVC
Gerrit Garbereder
45
45/6616.03.2012
4545
MVC
● Model-View-Controller Pattern● Kein „echtes“ Designpattern nach GoF
● Zusammengesetzt aus Observer- und Strategypattern● Trennung von Daten, Verarbeitung und Anzeige
vgl. EVA Prinzip● Viele Implementierungen nennen sich MVC, aber beachten wichtige
Kernaspekte nicht!● Model, View und Controller müssen austauschbar sein!● Unterschiedliche Implementierungen, manche erlauben keine direkte
Kommunikation vom Model zum View für den Gebrauch ist dieser Unterschied irrelevant
Gerrit Garbereder
46
46/6616.03.2012
4646
● Strategie
● Die Definition einer Strategieschnittstelle ist stets Domänen-abhängig
Gerrit Garbereder
47
47/6616.03.2012
4747
● Observer
Gerrit Garbereder
48
48/6616.03.2012
4848
MVC
● Observer als allgemeines Patternabstract class Observer{ abstract function aktualisiere(); }
class Observeable{ private $obs = array(); function meldeAn(Observer $o){ $this->obs[] = $o } function meldeAb(Observer $o){ for($i=0;$i<count($this->obs);++$i) if($this->obs[$i]==$o){ unset($this->obs[$i]); return true; } return false; } function benachrichtige(){ foreach($this->obs as $key => $value) $value->aktualisiere(); }}
Gerrit Garbereder
49
49/6616.03.2012
4949
MVC
Gerrit Garbereder
50
50/6616.03.2012
5050
MVC
● View muss Daten über das Modell aus dem Controller erhalten● z.B. Datenmenge
● Controller Schnittstelle muss unabhängig von der Modell-Implementierung sein, gleichzeitig aber Domänenspeziefisch!D.h.:● Controller für Gästebuch ist anders als Controller für Buchungssystem● Gästebuchcontroller muss aber sowohl mit Datei-basierten als auch Datenbank-
basierten Gästebuch arbeiten können
● Observerpattern kann allgemein implementiert werden● Model-View Beziehung nur auf Observer-Observeable Ebene● Das Model sagt dem View dass sich etwas geändert hat, die View fragt den
Controller was sich geändert hat bzw. nach den gesamten Daten● Beispiel: Adressbuch
Gerrit Garbereder
51
51/6616.03.2012
5151
MVC
● Für das Adressbuch werden Domänenspezifische abstrakte MVC Klassen geschriebenabstract class View extends Observer{}
abstract class Model extends Observeable{ // Domänen spezifisch abstract function schreibe($obj,$key,$value); abstract function lese($obj,$key); abstract function objekte();}
abstract class Controller{ // Domänen spezifisch bzgl. Modell // Werden i.d.R. Weitergegeben, müssen aber nicht z.B. READ-ONLY Controller abstract function schreibe($obj,$key,$value); abstract function lese($obj,$key); abstract function objekte(); // ergänzende Funktionen // erzeugt eindeutige ID als $obj und schreibt die Daten in das Model abstract function neuePerson($vorname,$nachname);}
Gerrit Garbereder
52
52/6616.03.2012
5252
MVC
class MyController extends Controller { private $model; function __construct($m) { $this->model = $m; } function schreibe($obj,$key,$value) { $this->model->schreibe($obj,$key,$value); } function lese($obj,$key) { return $this->model->lese($obj,$key); } function objekte() { return $this->model->objekte(); }}
class MyModel extends Model { private $data; function schreibe($obj,$key,$value){ $this->data[$obj][$key] = $value; parent::benachrichtige(); } function lese($obj,$key){ return @$this->data[$obj][$key]; } function objekte() { foreach ($this->data as $key => $value) $ret[] = $key; return $ret; }}
Gerrit Garbereder
53
53/6616.03.2012
5353
MVC
class MyView extends View { private $controller; function __construct($c){ $this->controller = $c; }
function aktualisiere(){ $obj = $this->controller->objekte(); foreach ($obj as $key => $value) { echo $this->controller->lese($value,"vorname")." "; echo $this->controller->lese($value,"nachname")."<br>"; } }}
$m = new MyModel();$c = new MyController($m);$v = new MyView($c);$m->meldeAn($v);
$c->schreibe("t1", "vorname", "Chuck");$c->schreibe("t1", "nachname", "Norris");
$c->schreibe("t2", "vorname", "John");$c->schreibe("t2", "nachname", "Wayne");
Gerrit Garbereder
54
54/6616.03.2012
5454
MVC
● Ausgabe Chuck // 1. Schreiben Chuck Norris // 2. Schreiben Chuck Norris // 3. Schreiben John // 3. Schreiben Chuck Norris // 4. Schreiben John Wayne // 4. Schreiben
● Problem:● Es wird immer ein Update gesendet
● Lösung:● Schnittstelle überarbeiten● Benachrichtigungen Ein- / Ausschalten● Nachrichten mit Informationen ausstatten
Gerrit Garbereder 16.03.2012
CakePHPCakePHP
Gerrit Garbereder
56
56/6616.03.2012
5656
CakePHP
http://book.cakephp.org/2.0/en/cakephp-overview/understanding-model-view-controller.html
● CakePHP ist ein PHP Framework● http://www.cakephp.org aktuell V 2.0.5
● CakePHP verwendet Namenskonventionen um Model, View und Controller zu verbinden● Die Konventionen steuern auch das Verhalten der Applikation
● Es wird MVC eingesetzt, aber CakePHP ist noch deutlich weitreichender
● Es muss MVC verwendet werden● CakePHP bietet nichts,
was PHP nicht kann
Gerrit Garbereder
57
57/6616.03.2012
5757
CakePHP
● CakePHP● app
– Model– Controller– View– …
● Zugriff auf die Daten via Array● Model Fieldecho $post['Post']['body']
● Einführung am Beispeil Blog● http://book.cakephp.org/2.0/en/tutorials-and-
examples/blog/blog.html
Gerrit Garbereder
58
58/6616.03.2012
5858
CakePHP
● Datenbank● Muss eine numerische eindeutide ID enthalten● Felder mit bestimmten Namen werden automatisch von CakePHP verwaltet
z.B. „created“, „modified“
Gerrit Garbereder
59
59/6616.03.2012
5959
CakePHP
● Datenbank● CakePHP erwartet eine Konfiguration der Datenbankzugangsdaten
class DATABASE_CONFIG { public $default = array( 'datasource' => 'Database/Mysql', 'persistent' => false, /*Art der Verbindung, nicht der Speicherung*/ 'host' => 'localhost', 'login' => 'root', 'password' => '', 'database' => 'cake', 'prefix' => '', );}
● Anlegen der Datenbank erfolgt wie gewohnt außerhalb per MySQL Workbench o.ä.● Tabellennamen sind per Konvention plurarl. Hier: posts● Spalten: id, title, body, modified, created
Gerrit Garbereder
60
60/6616.03.2012
6060
CakePHP
● Model - Post.phpclass Post extends AppModel { public $name = 'Post'; }
● Controller – PostsController.phpclass PostsController extends AppController { public $name = 'Posts';
public function index() { $this->set('posts', $this->Post->find('all'));}
● View – Views/Posts/index.ctp<?php foreach ($posts as $p): ?> <?php echo $p['Post']['title'] ?> <br/><?php endforeach; ?>
Gerrit Garbereder
61
61/6616.03.2012
6161
CakePHP
● diese < 20 Zeilen Code schaffen die Basis für den gesamten Blog und zeigen eine Liste alle Posts:
Bsp.Daten
DebugInfos
Gerrit Garbereder
62
62/6616.03.2012
6262
CakePHP
● Erweiterung des Controllers zum erstellen neuer Posts – controller/PostsController.php
public function add() { if ($this->request->is('post')) { if ($this->Post->save($this->request->data)) { $this->Session->setFlash('Your post has been saved.'); $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash('Unable to add your post.'); } }}
● Hinzufügen eines neuen Views – views/Posts/add.ctpecho $this->Form->create('Post'); // add als default actionecho $this->Form->input('title');echo $this->Form->input('body', array('rows' => '10'));echo $this->Form->end('Save Post');
Gerrit Garbereder
63
63/6616.03.2012
6363
CakePHP
Gerrit Garbereder
64
64/6616.03.2012
6464
CakePHP
● CakePHP erkennt automatisch ob hinzugefügt oder editiert werden soll. Abhängig davon ob das ID Feld vorhanden ist oder nicht.
● echo $this->Form->input('id', array('type' => 'hidden'));
● Dennoch muss zum editieren das Formular explizit die action übergeben bekommen
● echo $this->Form->create('Post', array('action' => 'edit'));
● Abhängig vom Datentyp wird der Typ des Inputfeldes automatisch bestimmt. z.B. Number für Integer
● Links sollten mittels „Html“ Helper Methoden generiert werden, da CakePHP automatisch mod_rewrite Regeln anlegt sollte der Server dies unterstützen● http://api.cakephp.org/class/html-helper
Gerrit Garbereder
65
65/6616.03.2012
6565
CakePHP
● Vorteile:● Schnelle Entwicklung● Saubere Trennung der Schichten durch MVC● Einfache Zusammenarbeit dank einheitlichen Namensgebung● Gute Wartbarkeit des Codes● Große Community
● Nachteile:● Viel passiert „automagic“, sodass der Entwickler ggf. nicht alle
Konsequenzen eines Befehls kennt● Ein zusätzliches Framework kann zusätzliche Fehler finden● Einarbeitungszeit in CakePHP
Gerrit Garbereder 16.03.2012
Danke für dieDanke für dieAufmerksamkeit!Aufmerksamkeit!