Ich habe mich schon öfter mit dem containable behaviour herumgeschlagen. Deshalb möchte ich hier einen kurzen Einstieg geben, denn der scheint nicht immer logisch und ist echt schlecht dokumentiert. Ich nutze für diese Einführung Cake 1.3.2.
Was macht containable? Es beschreibt, was ich von einem assoziierten Modell mit abfragen möchte. Dabei kann ich sowohl mehr als auch weniger als den Standard abfragen. Ich mache das mal an einem Beispiel klar.
Scenerie: ich habe Geräte (Devices) die alle eine eindeutige ID haben. Die wird per md5-Hash erzeugt und kann z.B. so aussehen:
- Code: Alles auswählen
- bf34aa79a32b168265782f95595af6e3
Diese Geräte werden in Kampagnen eingesetzt, die wiederum von Menschen betreut werden.
Ich beginne mit der einfachsten Funktion: Gerät abfragen und ausgeben (hier der Übersicht halber per "pr").
- Code: Alles auswählen
Per default werden auch die assoziierten Modelle mitgeliefert. Und zwar eine Ebene tief. Das Ergebnis wäre hier:
- Code: Alles auswählen
- Array
(
[Device] => Array
(
[id] => 1
[name] => 0001
[hash] => bf34aa79a32b168265782f95595af6e3
[campaign_id] => 1
[created] => 2010-07-26 01:43:47
[modified] => 2010-07-26 03:18:33
)
[Campaign] => Array
(
[id] => 1
[name] => Luisenviertel
[user_id] => 1
[created] => 2010-07-26 03:18:02
[modified] => 2010-07-26 03:18:02
)
)
Da ich $this->Device abfrage, schränke ich erst mal die Felder ein, die Cake von der Datenbank abfragt. Denn je mehr ich abfrage, desto langsamer wird die ganze Geschichte, wenn ich mal viele Beziehungen zwischen den Modellen habe. Dann sieht mein find so aus:
- Code: Alles auswählen
Und das Ergebnis so:
- Code: Alles auswählen
Schon besser. Nun will ich aber die Kampagne mitgeliefert bekommen, in der das Gerät eingesetzt wird. Womit wir zum containable behaviouor kommen. Das muss ich erst mal zur Laufzeit aktivieren:
- Code: Alles auswählen
- $this->Device->Behaviors->attach('Containable');
Diese Zeile schreibe ich vor dem find, da find sich danach richtet. Darauf folgt eine Zeile, in der ich dem Find sage, was es denn alles "containen", also enthalten soll:
- Code: Alles auswählen
- $this->Device->contain('Campaign');
Das geht auch an anderen Stellen wie z.B. dem app_controller. Schlagt dazu aber mal im Kochbuch nach. http://book.cakephp.org/de/view/1323/Containable
Zusammen sieht das so aus:
- Code: Alles auswählen
- [...]
$this->Device->Behaviors->attach('Containable');
$this->Device->contain('Campaign');
$currentDevice = $this->Device->find('first',
[...]
Ergebnis sieht nun wie folgt aus:
- Code: Alles auswählen
So weit, so gut. Verstanden? Wahrscheinlich schon. Jetzt will ich noch ein Feld hinzufügen:
- Code: Alles auswählen
- $this->Device->contain('Campaign.name');
- Code: Alles auswählen
Mehrere Felder? Kein Problem:
- Code: Alles auswählen
- $this->Device->contain('Campaign.name', 'Campaign.created');
- Code: Alles auswählen
- [...] [Campaign] => Array
(
[name] => Luisenviertel
[created] => 2010-07-26 03:18:02
[id] => 1
)
[...]
Beziehungen von Beziehungen? Also im Klartext: Wem gehört denn nun diese Kampagne?
- Code: Alles auswählen
- $this->Device->contain('Campaign.name', 'Campaign.User');
- Code: Alles auswählen
Dabei ist darauf zu achten, dass das verknüpfte Modell (in diesem Fall "User") mit einem Großbuchstaben beginnt. Kleinbuchstaben werden für ein Tabellenfeld gehalten. Aber der User hat bestimmt noch mehr Informationen - seine ID wollen wir ja nicht anzeigen. Bevor ich jetzt jedes Feld einzeln anspreche: hier kommen die Kniffe.
Nummer eins: ich erweitere die Abfrage des Modells um das Feld, in dem die ID des verknüpften Modells gespeichert ist. im Klartext: ich frage die campaign_id mit ab:
- Code: Alles auswählen
Ergebnis - so komisch das auch ist:
- Code: Alles auswählen
- Array
(
[Device] => Array
(
[id] => 1
[hash] => bf34aa79a32b168265782f95595af6e3
[campaign_id] => 1
)
[Campaign] => Array
(
[name] => Luisenviertel
[user_id] => 1
[User] => Array
(
[id] => 1
[email] => hier@da.com
[password] =>
[name] => Gallenkamp
[firstName] => Guido
[company] => bytethinker
[created] => 2010-08-06 04:11:33
[modified] => 2010-08-06 04:11:33
)
[id] => 1
)
)
Ich brauche aber bestimmt nicht alle Felder. Daher kann ich die Auswahl einschränken. Mit 'Campaign.User' habe ich ja das komplette Modell abgefragt. Nun kann ich, wie oben schon gezeigt, z.B. ein einzelnes Feld anfordern:
- Code: Alles auswählen
- $this->Device->contain('Campaign.name', 'Campaign.User.firstName');
Ergebnis:
- Code: Alles auswählen
Mehrere Felder? Mal probieren...
- Code: Alles auswählen
- $this->Device->contain('Campaign.name', 'Campaign.User.firstName', 'Campaign.User.name');
Ergebnis:
- Code: Alles auswählen
Nanu? Das zweite abgefragte Feld verschluckt das erste. Und damit kommen wir zum zweiten Kniff, der dieses ebenfalls unlogische Verhalten ausbügelt:
- Code: Alles auswählen
- $this->Device->contain('Campaign.name', 'Campaign.User.name,firstName');
Alle Feldnamen hintereinander, durch Kommata von einander getrennt, aber ohne (!) Leerzeichen dazwischen. Ergebnis:
- Code: Alles auswählen
Das soll es erst mal gewesen ein. Mit dem containable behavoir kann man sehr komplexe Abfragen abfrühstücken, die auch sehr verschachtelt mit Bedingungen versehen werden können. Die Möglichkeiten sind hier unendlich.
Aus Gründen der Performance setze ich immer das containable behavoir ein. So achte ich immer automatisch drauf, das ich keinen Datenmüll mit abfrage. Sollte die Anwendung dann in die Knie gehen, liegt das am Server und nicht an meinen Code, was ich als sehr zufriedenstellend empfinde
Grüße,
Dogo
Und noch was: ich vertiefe das gerne, wenn Bedarf besteht.