Daniel Thoma: PHP OOP - Strukturierung des "Models" in MVC

Beitrag lesen

Hallo frankx,

Den Front-Controller aber kann es ja nur einmal geben. Dadurch wird er dann auch zu so einer Art Registrierung, weil alle Plugins sich dort eintragen.

Ja, Sigletons sind ja auch nicht immer falsch. Man kann sich da nur leicht irren, weil man eine bestimmte Anwendung im Kopf hat. Das Problem entsteht auch weniger durch den Singleton an sich sondern dadurch, dass man darauf dann aus allen unmöglichen Stellen zugreift.

Weil denen dann Funktionen des Interfaces fehlen? Habe ich das Problem auch, wenn ich erst ein Iterface definiere und dann eine abstrakte Klasse als Implementierung, von der die anderen Model-Klassen erben?

Ich denke z.B. an so was: Du hast eine Tabelle Benutzer, eine Tabelle Gruppen und eine Zuordnungstabelle. Im Klassenmodell hast Du dann eine Klasse Benutzer und eine Klasse Gruppe. Wenn Du alle Benutzer-Objekte für eine Gruppe brauchst, würdest Du sowas machen:
Gruppe gruppe = BenutzerVerwaltung.getGruppe("name");
Benutzer[] benutzer = gruppe.getBenutzer();
Dazu müsstest Du intern zwei SQL-Queries ausführen: "select * from gruppen where name='name'" und "select * from benutzer as t1, gruppen2benutzer  as t2 where t1.id = t2.benutzer and t2.gruppe = gruppenid".

Mit SQL könntest Du direkt so etwas machen:
"select * from benutzer as t1, gruppen2benutzer as t2, gruppen as t3 where t1.id = t2.benutzer and t2.gruppe = t3.id and t3.name = 'gruppenname'"

Das lässt sich beliebig verkomplizieren, sodass Du beim objektorientierten Fall viel mehr Queries brauchst als bei direkter Verwendung von SQL.
Mit einem Object-Relation-Mapper könnten man Queries auf Objekt-Ebene formulieren:
Benutzer[] benutzer = orm.execute("from Benutzer benutzer where benutzer.gruppe.name = 'gruppenname'");

Ob jetzt SQL-Datenbank oder XML oder serialized-data ist dabei wohl egeal, oder?

Nein, jede Form von Serialisierung ist einfacher, weil man dabei schlicht alle Daten einliest und alle Abfragen dann auf dem Objektgraphen durchführt. Das Problem ist diese Möglichkeit, Daten schon in der Datenbank zu verarbeiten und im Programm selbst. Ob man eine relationale Datenbank oder eine XML-Datenbank hat, dürfte hingegen egal sein. Natürlich kann man auch bei einer Datenbank einfach erstmal alles auslesen, aber das ist wohl kein praktikabler Ansatz.

Hier verstehe ich vermutlich erst, wenn mir das mal wirklich unter die Finger kommt. Eine Abfrage über Tabellen hinweg könnte doch von einer weitern Klasse realisiert werden, die dann auf die "Tabellen"-Klassen zugreift, dachte ich.

Richtig, aber es ist oft weniger effizient. Siehe obiges Beispiel. Das führt dazu, dass man letzten endes alle Joins auf dem Objektmodell berechnet, statt das von der Datenbank machen zu lassen.

Zend bietet nur "V" und "C", kein "M". "model can be anything".

Ja, das heißt wohl "Model muss man selber machen" oder eben gar nicht. Wobei gar nicht je nach Anwendung durchaus sinnvoll sein kann, nach meiner Auffassung.

Zumindest ist das ein sicher nicht unwesentlicher Unterschied zu RoR.

Mit RoR habe ich mich noch nicht beschäftigt, aber die scheinen wohl etwas zu machen, was dem Object-Relation-Mapping-Ansatz entspricht.

interface My_Model_Interface
{
    function getForm();
    function create();
    function read();
    function update();
    function getList();
}

Das sieht ziemlich prozedural aus. Kann natürlich dennoch sinnvoll sein. Für obiges Benutzerbeispiel hättest Du wohl sowas:  
interface Benutzerverwaltung {  
   Gruppe getGruppe(name);  
   Benutzer getBenutzer(name);  
   Benutzer[] getBenutzerForGruppe(name);  
   ...  
}  
Sicher sinnvoll, man kann sicher auch dafür etwas Objektorientierung nutzen, um die Art der Datenhaltung austauschbar zu machen, das ist aber kein Datenmodell.  
Ein Datenmodell würde einzelne Datensätze einer Tabelle jeweils als Objekt repräsentieren. Eben eine Klasse Benutzer und eine Klasse Gruppe, wobei eine Instanz einer solche Klasse genau einen konkreten Benutzer oder eine Gruppe repräsentieren.  
  

> Dann würden die Controller über diese Schnittstelle auf Daten zugreifen und "wüssten" nicht, ob dabei das Model die Daten aus einer XML-Datenbank oder SQL-Datenbank oder einem serialisierten File holt.  

Ja, aber er könnte keine Abfrage formulieren, für die Du keine Methode vorgesehen hast. Damit musst Du letzten endes alle erdenklichen Abfragen als Funktionen bereitstellen. Wenn eine neue Komponete eine weitere Abfrage braucht, musst Du diese hinzufügen, Du kannst sie nicht in der Komponente selbst implementieren.  
  

> Ich dachte, so würde man Modularität ermöglichen. Ich könnte das Model unabhängig von den Controllern ausbauen, ohne je bei den Controllern was änderen zu müssen.  

Ja, das ist sicher eine Form von Modularität. Aber umgekehrt möchte man natürlich auch ändern können, was die Controller mit den Daten tun, ohne am Modell etwas ändern zu müssen, jedenfalls zu einem gewissen Grad.  
  

> Zeichnet sich Modulariät nich dadurch aus, dass man immer die Möglichkeit hat, weitere Schnittstellen anzubieten (zB. innerhalb des Models) um Spezialaufgaben an u.U. neue Klassen zu deligieren, \_ohne\_ die bereits vorhanden Klassen dadurch zu berührern?  

Ja und dadurch, die Implemetierung bestehender Klassen ändern zu können, ohne darauf aufbauende Klassen ändern zu müssen. Das erfüllt Dein Ansatz aber nur eingeschränkt, Du musst Die Modell-Klasse ändern, wenn der Controller weiter Abfragen ausführen muss.  
Wenn Du mehrere Modell-Klassen für unterschiedliche Funktinalitäten hast (Beispiel: Benutzerverwaltung, Dokumentverwaltung) gibt es zudem keinen logischen Ort, wo man bestimmte Queries unterbringen könnte. Wenn Dokumenten beispielsweise eine Benutzergrupppe zugeordnet ist, die das Dokument ändern darf, hätte man eventuell gerne so eine Funktion:  
boolean canUserChangeDocument(docid, userid);  
Wenn man das wieder mit einem SQL-Query bestimmen will, muss man wissen, wie Benutzer und Gruppen zugeordnet werden und wie Dokumente Gruppen referenzieren. Bringt man die Methode in der Klasse für Dokumente unter, muss man diese Klasse ändern, sobald man z.B. die Benutzerverwaltung so ändert, dass Gruppen wiederum Gruppen enthalten können o.ä. Bringt man die Methode in der Klasse der Benutzerverwaltung unter, hat man die Methode da drin, selbst wenn man die Komponente Dokumentenverwaltung gerade gar nicht benötigt. Ändert man etwas an der Dokumentenverwaltung, z.B. mehrere Gruppen können einem Dokument zugeordnet werden, muss man zudem auch die Benutzerverwaltung ändern.  
Das ist, was ich mit schlechter Modulariät meine.  
  
Für eine typische Webanwendung aus einem Guss muss das wie gesagt kein Problem sein. Wenn man diese Modularität gar nicht braucht, kein Objektmodell braucht, um darauf irgendwelche komplexeren Verfahren zu implementieren, dann ist Dein Ansatz durchaus eine vernünftige herangehensweise. Man muss die Dinge ja nicht komplizierter machen, als sie schon sind.  
  
Ich hab' gerade mal nach ORM-Werkzeugen für PHP gesucht und hab' das hier gefunden: <http://ezcomponents.org/docs/tutorials/PersistentObject>  
Keine Ahnung, wie gut das ist, ich kenne solche Dinge nur von Java, beispielsweise: <http://www.hibernate.org/>  
Auch ORM löst nicht alle Probleme und bringt auch erstmal zusätzliche Komplexität mit sich, es lohnt sich aber bestimmt, sich das mal anzusehen.  
  
Grüße  
  
Daniel