Man kann nicht nur über die composer.json den Autoloader befüllen. Wenn man z.B. Module hat die nicht über den Composer installiert werden sondern aus einem Verzeichnis geladen werden, müsste man die Einträge per Hand vornehmen, was aber nicht wirklich ideal ist.
Lange Zeit habe ich auch immer bei Columns von Datenbank-Tabellen immer ein Prefix verwendet. Die Tabelle TESTS hatte dann z.B. die Spalte TEST_ID. Daran ist ja auch erstmal nichts verkehrt. Probleme gab es mit Oracle und der Beschränkung auf 30 Zeichen für den Spaltennamen in einigen Fällen.
$entity->setId($data["TEST_ID"]);
Der Code oben funktioniert auch super, wenn man jedes Value "per Hand" in das Object schreibt.
Wenn wir aber nun ein automatisches Mapping über Annotationen nutzen kann es in einigen Fällen schnell umständlich werden.
Wenn wir keine Ableitung und Vererbung benutzen, ist auch hier kein Problem zu erwarten. Wenn wir aber eine Basic-Klasse verwenden, von der alle anderen Klassen ableiten, haben wir schnell ein Problem, weil wir in der Basic-Klasse in den Annotationen einen Platzhalter für den Prefix verwenden müssten. Ohne Prefix geht es hier sehr viel einfacher.
/**
* @dbcolumn=ID
*/
private $id=0;
/**
* @dbcolumn={prefix}_ID
*/
private $id=0;
Deswegen sollten Prefixe nur bei FKs verwendet werden, um ein automatisches Mapping unkompliziert nutzen zu können. Wenn man es jetzt nicht nutzt, will man es später vielleicht und kann dann jetzt schon alles so bauen, um später keine Probleme zu bekommen. Platzhalter funktionieren natürlich auch.. gehen aber zur Lasten der Performance.
Gerade im Zend Framework 2 kommt man immer mehr in Bereiche wo man auf altbekannte Konzept aus der Enterprise Welt und dort auch auf viel bekanntes aus Java (EE) trifft. Neben Depenency-Injection und Factories, findet man immer wieder gute Möglichkeiten Konzept wie DAO oder
auch POPOs als PHP-Variante der POJOs anzuwenden (wobei ich hier POJO und Bean gleichsetzen würde).
Auch Getter und Setter gehören hier genau so zum Alltag wie in Java.
Ich möchte hier ein paar Beispiele geben, wie man eine einfache Umsetzung in einem ZF2-Modul realsieren kann. Ich gehe einfach mal davon aus, dass man in der Lage ist ein einfaches Module in ZF2 zu erstellen und auch sich mit Controllern ViewModel und TableGateway zu mindest einmal beschäftigt hat. Der Einstieg ist nicht besonders einfach, wenn man keine lauffähige Anwendung hat, in die man sich einarbeiten kann.
Die hier umgesetzten Ansätze, sollen die Entwicklung möglichst beschleunigen und viel Code sparen, der in vielen Beispielen auch mit drin ist und alles sehr unübersichtlich macht. Auch wenn hier einige Ableitungen vorkommen halte ich das exesive Ableiten und Vererben für einen falschen Ansatz. Factories und Dependency-Incetion halte ich für einen besseren Weg eine Klasse mit bestimmten Eigenschaften auszustaten.
Ich leite auch vom TableGateway ab, wobei die bessere Methode es wohl wäre in der Factory mit Hilfe des ServiceManagers ein TableGateway zuerstellen und dieses an den entsprechenden Constructor zu übergeben.
Aber fanden wir einfach mal mit dem Model bzw der Entität an.
public function setName($name){
$this->_name=$name;
}
public function getValue(){
return $this->_value;
}
public function setValue($value){
$this->_value=$value;
}
}
Die voran gestellen _ entsprechen dem Zend Code-Style und sollen private und protected Attribute der Klasse deutlicher hervor haben.
Eigentlich sollte ja jedes Attribute der Klasse private oder protected sein. Nur wer die PHP eigenen JSON Funktionen oder Klassen verwendet, könnte man in die Verlegenheit kommen etwas public zu machen, aber auch hier gibt es bessere Lösungen.
Ich halte wenig davon alle Attribute grundsätzlich auf protected zu setzen, nur weil die Möglichkeit besteht mal eine Klasse ableiten zu wollen.
In den meisten Fällen, sollte man dann überlegen, ob es wirklich eine andere Entität ist oder man die vorhandene erweitern oder anpassen könnte so, dass keine weitere sehr ähnliche Entität nötig wird. Ableiten von einer BasicEntity oder einem BasicModel ist natürlich sinnvoll. Aber konkrete fachliche
Entitäten abzuleiten ist meistens weniger sinnvoll.
Nun kommen wir zu einem Punkt, der in der ZF2-Welt oft anders gesehen wird. Ich leite das Model nicht direkt vom TableGatway ab. Das Problem ist hier, das man für das TableGateway einen DBAdapter benötigt den man mit Hilfe des ServiceManagers bekommt. Also muss man beim Erzeugen des Model-Objekts immer diesen übergeben und das TableGateway instanzieren. Das kann man natürlich super mit den Factories des ZF2 erledigen. ABER.. wenn ich eine Liste des Models aus der DB lade und in einer Schleife mir für jede Row aus der DB eine Instanz der Klasse erzeuge ist das Vorgehen jede Instanz über diesen Factory bauen zu lassen, alles anderes als elegant oder schnell.
Also bleiben wir bei unserem einfachen POPO und erstellen für die Zugriffe auf die Datenbank nutzen wir eine weitere Klasse. Für jedes Model/Entity erzeugen wir uns eine DAO-Klasse. Diese enthält alle Methoden zum Laden, Speichern und Löschen sowie Listen laden des Models. Wie für fast jedes DAO benötigen wir hier eine Factory um die Einstellungen des Systems (DB-Verbindung) dem DAO bekannt zu machen.
Die Factory tragen wir in der module.config.php ein, die wir in der Methode getConfig der Module.php laden.
$sm ist der ServiceManager, den wir hier an den Constructor des DAOs übergeben. Der Constructor nutzt diesen um den DBAdapter zu bekommen und an das TableGateway weiterzugeben. Wie schon weiter oben geschrieben, hätte man dies auch in der Factory machen können und das TableGateway hier an das DAO übergeben können. Könnte einen etwas Code sparen es so zu machen.
Nun möchte man den DAO in dieser Art und Weise gerne verwenden:
Dafür schreiben wir uns jetzt einen eifnachen DAO, der erstmal nur die load-Methode enthält:
class XWTestDAO extends TableGateway{
$this->_sm=null;
public function __construct($serviceManager){
$this->_sm=$serviceManager;
parent::__construct(
$this->_tableName,
$serviceManager->get('dbAdapter')
);
}
//eine einfache Methode zum Kopieren der Daten aus dem Array in das Objekts
private function _copyData($entity,$array){
$dbm=new XWDBMapper();
return $dbm->setValuesFromArray($entity,$array);
}
public function loadTest($id){
$entity=new XWTest();
if($id!=null && $id>0){
$rsetItem=$this->select(
array('test_id' => $id)
)->current();
Ideal wäre es hier, wie man deutlich sehen kann 'test_id' auch später mal durch Annotationen zu ersetzen. Dafür wäre eine Annotation wie @tableid dann geeignet nach der man sucht und sich dann den @dbcolumn-Eintrag aus dem selben DocComment-Block heraus liest.
Aber so weit ist es ja schon mal ganz schön. Wie brauchten kein Array zu erzeugen und sonst haben wir wenig Code schreiben müssen.
Das wichtigste fehlt hier aber natürlich noch. Wie mappe ich die Annotationen auf das Array und umgekehrt?
Dieser Mapper ist ansich auch nicht wirklich kompliziert und basiert hauptsächlich darauf mit der RefelcetionClass zu arbeiten und mit deren Hilfe die ReflectionProperties zu bekommen.
class XWDBMapper{
public function getColumnNameFromDoc($doc){
$result=null;
if(preg_match("/@dbcolumn=[a-zA-Z0-9_]+/",$doc)){
$result=preg_replace("/^.+@dbcolumn=([a-zA-Z0-9_]+)\s.+$/Uis","$1",trim($doc));
}
return $result;
}
Sehr wenig Code und erleichtert einen das Leben sehr. Ist auch relativ schnell. Das händische Schreiben bzw Mappen der Arrays war doch immer sehr fehleranfällig und wenn man mal eine Column zur Datenbanktabelle hinzugefügt hat, müsste man an vielen stellen diese nachpflegen und hoffen, dass man keine Stelle vergessen hat. Jetzt muss man nur noch eine Annotation einbauen und es wird immer richtig gemappt.
Nachdem wir also nun das Laden einmal durchgegangen sind, kommt hier nur kurz die Methode für den Mapper, wie man ein Array aus einem Objekt erhält (die meisten werden schon wissen wie man die Methode da oben umdrehen kann). Deswegen brauche ich hier wohl auch nicht viel dazu schreiben.
Um jetzt alles noch schneller zu machen könnte man sich einen Cache für die Propert-Liste schreiben, dass diese nicht immer und immer wieder erzeugt werden muss. Beim Laden von Listen wäre das ein großer Vorteil. Aber selbst so ist alles schon sehr schnell. Jeden Falls habe ich zwischen den beiden Varianten bis jetzt in der Benutzung der Anwendungen keinen gravierenden Unterschied feststellen können, was die Performance betrifft. Mit XDebug durch gemessen habe ich noch nicht.
So haben wir mit nur eine paar kleinen Zeilen Code uns viel Arbeit erspart.. vielleicht habe ich hiermit noch anderen deren Arbeit vereinfacht.
Beim Zend Framework 2 muss man sich ja wieder mit ReWrite-Rules und sowas auseinander setzen. Beim der REST-Implementierung in meinem Framework habe ich die ja auch benutzt und mußte mich erstmal wieder mit dem Syntax auseinander setzen um auch die Request und Get-Parameter mit zu übergeben. Ich fragte mich dann warum ich nicht von anfang an darauf gesetzt hatte, weil man so ja auch Domain mit Text und Namen drin einfach realiseren kann.
Nachdem ich dann beim Zendframewprk wieder damit kämpfen durfte, war es mir wieder klar. Get-Parameter sehen nicht toll aus, aber funktionieren immer! Wirklich immer egal auf welchen Server und mit welcher Konfiguration auch immer!
Das Zend Framework macht ja nichts anderes als mein Framework und biegt die URLs auf die index.php um und liefert dann die ursprüngliche URL zum Parsen mit. Vielleicht sollte ich doch noch mal die Rules meiner htaccess weiter verbessern, dass man doch Pages mit Namen und weiterer Beschreinung wie einer Überschrift aufrufen kann (die Beschreibung dann dynamisch auslesen oder so.. was auch bei URLs im Blog-Module toll wäre).
Wenn ich auch das Servlet-Mapping aus dem Tomcat mit dem ReWrite-Rules im Apache vergleiche.. der Servlet-Container macht es einem doch sehr viel einfacher. Da geht mal ein Punkt an die Java-Welt.
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?