Empfehlungen: Erweiterte Suche

Einführung und Kniffe: Containable

Anleitungen und Quellcode-Auszüge die den Start vereinfachen sollen.

Einführung und Kniffe: Containable

Beitragvon Dogo » Fr 6. Aug 2010, 04:57

Hallo zusammen,

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
Wenn ich diesen Hash hinter meine URL hänge, dann wird dieses Gerät abgefragt und zurückgegeben.
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
function hash2tree($hash = NULL) {
    $hash = $this->params['url']['hash'];
    $currentDevice = $this->Device->find('first',
        array(
            'conditions' =>
                array(
                    'Device.hash' => $hash
                )
            )
    );
    if (isset($currentDevice)) {
        pr ($currentDevice);
//      return ($currentDevice);
    }
    else {
//      return false;
    }
    }


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
    $currentDevice = $this->Device->find('first',
        array(
            'conditions' =>
                array(
                    'Device.hash' => $hash
                ),
            'fields' =>
                array(
                    'id', 'hash'
                    )
            )
    );

Und das Ergebnis so:
Code: Alles auswählen
Array
(
    [Device] => Array
        (
            [id] => 1
            [hash] => bf34aa79a32b168265782f95595af6e3
        )

)
 

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
Array
(
    [Device] => Array
        (
            [id] => 1
            [hash] => bf34aa79a32b168265782f95595af6e3
        )

    [Campaign] => Array
        (
            [id] => 1
        )

)
 

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
[...][Campaign] => Array
        (
            [name] => Luisenviertel
            [id] => 1
        )
[...]

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
    [Campaign] => Array
        (
            [name] => Luisenviertel
            [user_id] => 1
            [id] => 1
        )
 

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
            'fields' =>
                array(
                    'id', 'hash', 'campaign_id'
                    )

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
[...]
            [User] => Array
                (
                    [firstName] => Guido
                )
[...]
 

Mehrere Felder? Mal probieren...
Code: Alles auswählen
    $this->Device->contain('Campaign.name', 'Campaign.User.firstName', 'Campaign.User.name');

Ergebnis:
Code: Alles auswählen

            [User] => Array
                (
                    [name] => Gallenkamp
                )
 

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
            [User] => Array
                (
                    [name] => Gallenkamp
                    [firstName] => Guido
                )
 


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.
Benutzeravatar
Dogo
 
Beiträge: 63
Registriert: Do 6. Aug 2009, 00:59
Wohnort: Wuppertal

Re: Einführung und Kniffe: Containable

Beitragvon Jörg » Mi 18. Aug 2010, 21:54

Hi Dogo,

vielen Dank für deine Mühen und die detaillierte Beschreibung und Umsetzung von Containable's.
Jörg
 
Beiträge: 105
Registriert: Di 13. Mai 2008, 13:45
Wohnort: Bremen
CakePHP-Version: 2.0.3, 1.3.x
OS: Win7/Ubuntu


Zurück zu Tutorials und Snippets

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 0 Gäste

cron