Zugriff auf die gleiche Tabelle über eine 2. Klasse

Hallo Staff,

das war genau meine erste Idee, funktioniert aber nicht, scheinbar weil ich aus dem Behavior heraus das Behavior abschalten müsste, da ich prinzipiell das Behavior benötige, um nach dem Finden des Datensatzes ID’s zu ersetzen. D.h. ich führe die find()-Methode aus, lande in der Behavoir-Methode afterFind und versuche dort, ein findByPk() auf einen foreignKey auszuführen. Das klappt prinzipiell, aber nach dem findByPk() wird natürlich wieder die afterFind()-Methode des Behaviors aufgerufen, daher die Endlosschleife.

Mein erster Versuch war, das Behavior in der afterFind()-Methode des Behaviors mit disableBehavior() oder detachBehavior() auszuschalten, das zeigte allerdings keinerlei Wirkung. Ich nehme also einfach mal an, dass das Abschalten unmöglich ist, während man gerade mittendrin ist.

Das ist meine Behavior-Klasse:

[i]<?php

class AutoCompleteStammdatenBehavior extends CActiveRecordBehavior

{

private &#036;objCrmStammdaten ;


/**


 * ersetzt vor dem Speichern Felder gegen ihre id in crm_tree, überschreibt dazu die Objekteigenschaften von


 * &#036;this-&gt;owner


 * 


 * @return void


 */


public function beforeSave() {


/** aufrufende Klasse auf Verknüpfungen zu CrmStammdaten überprüfen und die gewaehlten id_stamdaten übernehmen **/


&#036;relations = &#036;this-&gt;owner-&gt;relations() ;


if (&#33;is_array(&#036;relations))


	return false;


	


foreach (&#036;relations as &#036;k =&gt; &#036;v) {


	if (&#036;v[1] &#33;= 'CrmStammdaten')


		continue ;


		


	// wurde per autocomplete ein Eintrag gewaehlt?, diesen aus dem hidden-Field übernehmen


	&#036;keyId = &#036;this-&gt;_getDivId(&#036;v[2],'_id') ;


	


	if (isset(&#036;_POST[&#036;keyId]) &amp;&amp; helper::isNumeric(&#036;_POST[&#036;keyId])) 


			&#036;this-&gt;owner-&gt;&#036;v[2] = &#036;_POST[&#036;keyId] ;


		else 


			&#036;this-&gt;owner-&gt;&#036;v[2] = 0 ;


}





}





/**


 * nach dem Auslesen aus der Datenbank die id's wieder in Strings übersetzen


 * @return void	


 */


public function afterFind() {	


&#036;relations = &#036;this-&gt;owner-&gt;relations() ;





if (&#33;is_array(&#036;relations))


	return false;


	


&#036;this-&gt;_createStammdatenObj() ; // Objekt zum Einlesen der Adressen über die 2. Model-Klasse


foreach (&#036;relations as &#036;k =&gt; &#036;v) {


	if (&#036;v[1] &#33;= 'CrmStammdaten')


		continue ;


	if (&#33;helper::isNumeric(&#036;this-&gt;owner-&gt;&#036;v[2]))


		continue ;	


	// Anschrift einlesen 


	&#036;key = &#036;this-&gt;_getDivId(&#036;v[2]) ;


	&#036;data = &#036;this-&gt;_findByPk(&#036;this-&gt;owner-&gt;&#036;v[2]);


	if (&#036;data instanceof CrmStammdatenOnly)


		&#036;this-&gt;owner-&gt;&#036;key = &#036;this-&gt;owner-&gt;getAddress(&#036;data) ;	


	


  // hidden-Field zur Speicherung der crm_stammdaten:id_stammdaten des verknüpften Datensatzes


	&#036;key2 = &#036;this-&gt;_getDivId(&#036;v[2],'_id') ;


	&#036;this-&gt;owner-&gt;&#036;key2 = &#036;this-&gt;owner-&gt;&#036;v[2] ;	


	


	// Originalfeld leer machen, es dient zur Suche


	&#036;this-&gt;owner-&gt;&#036;v[2] = '' ;


}


}


/**


 * erzeugt ein Objekt vom Typ Model CrmStammdaten (ohne Behavior AutoCompleteStammdatenBehavior)


*/	


private function _createStammdatenObj() {


&#036;this-&gt;objCrmStammdaten = new CrmStammdatenOnly(true) ;		


}


/**


 * ermittelt den Datensatz der übergebenen &#036;id_stammdaten über das CrmStammdaten-Objekt (z.B. Finanzamt, abw. Versandadresse), damit das Behavior AutoCompleteStammdatenBehavior nicht aufgerufen wird (die afterFind-Methode führt sonst zur Endlosschleife)


 * @return CrmStammdaten model


*/	


private function _findByPk(&#036;id) {


if (&#33;helper::isNumeric(&#036;id))


	return false;


return &#036;this-&gt;objCrmStammdaten-&gt;findByPk(&#036;id);


}


/**


 * html-id des Elements ermitteln, ueber welches die Anschrift des Stammdatensatzes angezeigt wird


 * @return string if des div-Elements	


*/


private function _getDivId(&#036;name,&#036;divName='_view') {	


&#036;classname = get_class(&#036;this-&gt;owner) ;


return &#036;classname.'_'.&#036;name.&#036;divName ;	


}

}[/i]

Und so implementiere ich sie in der CActiveRecord-Klasse:

class CrmStammdaten extends CActiveRecord

{

public function behaviors(){


    return array(


        'AutoCompleteStammdatenBehavior' =&gt; array(


            'class' =&gt; 'application.modules.crm.components.AutoCompleteStammdatenBehavior'


        )          


    );

}

Um die anderen Daten nachzuladen ist ein neues Objekt vermutlich sowieso von nöten. Demnach könntest du sowas machen:





public function afterFind()

{


   // Wenn nötig Daten nachladen

   $model = Model::model();

   $model->detachBehavior('AutoCompleteStammdatenBehavior');

   $model->findByPk(...);


}



Ob du damit was anfangen kannst weiss ich nicht (schwer durch deinen code durchzusteigen ohne code-tags).

CrmStammdaten ist eine ganz normale CActiveRecord-Klasse die ich per Model- und CRUD-Generator erzeugt habe.

Ich hätte jetzt fast gewettet, dass das so funktioniert, aber auch der Versuch, das Behavior vom neu erzeugten

Objekt zu entfernen, scheitert und führt damit zur Endlosschleife.

Naja, die Weg über die einfache Model-Klasse funktioniert und wenn nicht alles dagegen spricht,

werde ich so arbeiten. Verstoße ich damit gegen irgendwelche Konventionen?

Eingekürzt versuche ich das:

[i]class AutoCompleteStammdatenBehavior extends CActiveRecordBehavior

{

public function afterFind() {

$model = new CrmStammdatenOnly(true) ; // neues Model erzeugen auf Model-Klasse ohne Behavior

#$model = new CrmStammdaten() ; // Klappt nicht, erzeugt Endlosschleife

#$model->detachbehavior(‘AutoCompleteStammdatenBehavior’);

$data = $model->findByPk($this->owner->finanzamt_id); // Datensatz des Finanzamts aus der gleichen Tabelle holen

#$this->owner->finanzamt_id_view = $this->owner->getAddress($data) ; // Zweck des Ganzen: Adresse Finanzamt

}

}[/i]

Hi anett,

bitte verwende doch die Code-Tags (entweder [ code ] und [/ code ] ohne Leerzeichen um deine Codeblöcke setzen oder Codebeispiel markieren und den <> Knopf in der Buttonleiste des Editors drücken).

Deine Beispiele sind sonst so schwer zu lesen, dass einem eigentlich sofort die Lust darauf vergeht ;)

Oh Gott, jetzt hab ichs verstanden. Sorry


class AutoCompleteStammdatenbehavior extends CActiveRecordbehavior { public function afterFind() { $model = new CrmStammdatenOnly(true) ; // neues Model erzeugen auf Model-Klasse ohne behavior  #$model = new CrmStammdaten() ; // Klappt nicht, erzeugt Endlosschleife #$model->detachbehavior('AutoCompleteStammdatenbehavior');  $data = $model->findByPk($this->owner->finanzamt_id); // Datensatz des Finanzamts aus der gleichen Tabelle holen #$this->owner->finanzamt_id_view = $this->owner->getAddress($data) ; // Zweck des Ganzen: Adresse Finanzamt } } 

und jetzt nochmal, hoffentlich mit Zeilenumbrüchen:


class AutoCompleteStammdatenbehavior extends CActiveRecordbehavior

{

public function afterFind() {

$model = new CrmStammdatenOnly(true) ; // neues Model erzeugen auf Model-Klasse ohne behavior


#$model = new CrmStammdaten() ; // Klappt nicht, erzeugt Endlosschleife

#$model->detachbehavior('AutoCompleteStammdatenbehavior');


$data = $model->findByPk($this->owner->finanzamt_id); // Datensatz des Finanzamts aus der gleichen Tabelle holen

#$this->owner->finanzamt_id_view = $this->owner->getAddress($data) ; // Zweck des Ganzen: Adresse Finanzamt

}

}

Naja wenns mit deiner Lösung klappt ist doch okay B) Ich werde mir das aber mal bei Gelegenheit nachbauen und gucken obs noch besser geht.

Klar, bessere Lösungen sind immer gut :)

Mit einem Pseudo-Szenario wie Y!! das vorgeschlagen hat, müsste das recht einfach so gehen:


// Im behavior:

public function afterFind() {

    if ($this->owner->scenario==='autoComplete')

      return;


    $model=new CrmStammdaten('autoComplete');

    ...

}

Damit brauchst du keine zweite AR-Klasse mehr anlegen.

Noch eine Anmerkung am Rande: Falls du den deutschen Guide verwendest, freuen wir uns immer über Feedback bzw. Verbesserungsvorschläge zur Übersetzung. Davon kam bisher kaum was, daher nochmal die Anregung: Wenn was auffällt, einfach verlauten lassen. ;)

Ich hab den Versuch mit dem Scenario gestartet, allerdings steht das scenario in jedem Durchlauf der Endloschleife auf ‘update’, weil ich die actionUpdate() aufrufe.

Ich hab jetzt natürlich keinen Schimmer, an welcher Stelle ich das Scenario ändern könnte, so dass es beim Aufruf der afterFind()-Methode so umgeschrieben wird, dass es den Namen z.B. des Behaviors? trägt.

Wie dem auch sei, ich entwickle zum Einstieg in yii eine Adressverwaltung, die ich dann auch hier zur Verfügung stellen will, aber der jetzige Stand ist natürlich noch sehr sehr stark im Anfangsstadium.

Trotzdem, wenn es eine Möglichkeit gibt, ihn irgendwo einzuspielen, so dass jeder ihn sehen kann, um so zu verstehen, wovon ich rede, würde ich auch den jetzigen Stand mal einspielen.

Die Frage ist: Gibt es so eine Möglichkeit? In die Module will ich es nicht einstellen, dafür ist es noch lange nicht so weit.

Ich benutze die aktuellste YII-Version.

Jo, stimmt, so einfach gehts dann doch nicht. Vergiss meinen Vorschlag von oben. War zu schnell gedacht … ;)

Wegen Code posten: Dafür gibts noch keinen Bereich. Ich weiß auch nicht, ob die Site ein Coderepository für allen möglichen Beispielcode sein soll. Aber du kannst natürlich immer ein Google-Code Projekt eröffnen und deinen Code dort veröffentlichen.

Danke für den Hinweis. Habe mich für einen eigenen kostenlosen Webspace unter http://www.bplaced.net entschieden und werde dort mal versuchen, yii zu installieren.

Ich gebe übrigens zu, dass ich an der Lösung meines Problems fast einen ganzen Tag gebastelt habe, was ich auch nicht vermutet hätte, da ich wie gesagt vorher das Behavior entfernen oder unwirksam machen wollte, dann habe ich noch einen Versuch mit Vererbung gestartet, bei dem ich im Prinzip alles zum Laufen gebracht habe mit dem kleinen Fehler, dass danach der Datensatz nicht mehr gespeichert wurde (das Speichern sollte dann über die erbende Klasse passieren). Da ich zu dem Thema nichts gefunden habe, dachte ich, es wäre sinnvoll, es zu veröffentlichen, vielleicht hat ja mal jemand ein ähnliches Problem.

Man will ja nicht 10 Tabellen mit unterschiedlichen Adressen für Finanzämter, Amtsgerichte, Mitarbeiter usw. anlegen, sondern Adressen in einer Tabelle verwalten.

Und damit ergeben sich natürlich Abhängigkeiten auf die gleiche Tabelle.

Wenn mein Versuch läuft, gebe ich hier die Adresse noch mal an, dann wird es vermutlich leichter vorstellbar.

Wenn ich mir dein Problem nochmal so durchlese:

Warum definierst du nicht einfach eine Relation zur Adresstabelle in alle Records, die eine Adresse haben? Dann kannst du mit with(‘adresse’) bei allen Records die Adressen als relationales Record mitladen. Und das ganze benötigt nur eine einzige Abfrage.

Dein Behavior benötigt eine weitere DB-Abfrage pro Record, ist also alles andere als optimal.

Ich schätze, Du zielst in diese Richtung? (Klar, das ist die SQL-Variante).

Das werde ich auf jeden Fall testen. Da nur ein Bruchteil der Datensätze wirklich eine abw. Anschrift, einen gesetzlichen Vertreter, ein Finanzamt usw. besitzen, werde ich mal prüfen, welche Variante am Ende bei vielen Datensätzen schneller ist.

[sql]select a.*,

b.ort as finanzamt_ort,b.strasse as finanzamt_strasse,

c.ort as abwadresse_ort,c.strasse as abwadresse_strasse

from crm_stammdaten a

left join crm_stammdaten b ON a.finanzamt_id = b.id_stammdaten

left join crm_stammdaten c ON a.id_stammdaten_versandadr = c.id_stammdaten[/sql]

Die JOIN Variante ist auf jeden Fall schneller, da du nur 1 Abfrage brauchst, statt deiner 1+n Abfragen. Die Relationen sind ja genau dafür da. Also gibts eigentlich keinen Grund, komplizierte afterFind-Konstrukte zu bauen.

:rolleyes: weiß ich doch, aber ich wollte unbedingt mit Behaviors arbeiten.

Ich finde diese Möglichkeit einfach faszinierend, man kann so die Fähigkeiten einer Klasse genial erweitern, ich habe das so das erste Mal bei YII kennengelernt.

Normalerweise muß man vererben, kann aber leider nicht von mehreren Klassen erben.

Braucht man eine Funktion in verschiedenen Kindklassen, landet die Funktion in der Elternklasse, wenn nicht gerade eine statische Auslegung möglich ist.

Dadurch wuchsen meine Klassen bisher doch manchmal ganz schön an.

Jetzt ist es möglich, ganz klar abzutrennen und auch winzigste Einheiten zu schreiben.

Ich kenne YII erst seit 1,5 Monaten, aber ich finde es einfach Klasse.

Klar, gehört jetzt nicht mehr zum Thema, musste jetzt aber mal sein.