Zugriff auf die gleiche Tabelle über eine 2. Klasse

Das ist keine Frage sondern eher ein Tip. Mir ist klar, dass das eigentlich nicht hier her gehört, aber mein Englisch ist so schlecht, dass ich nicht unter Tipps schreiben wollte.

Wenns nicht stört, würde ich gern Problemlösungen weiter hier beschreiben, dann findet man sie an der richtigen Stelle wieder. Wenn’s das Forum stört, lasse ich es natürlich.

Problemstellung: Es gibt eine Tabelle crm_stammdaten. Diese enthält ALLE Adressen. In der Tabelle existieren Foreign-Keys die Tabelle selbst (crm_stammdaten), z.B. für die Verknüpfung zu einer abw. Versandadresse, zu einem Finanzamt oder zu einem gesetzlichen Vertreter.

Diese Adressen sollen im Formular per autocomplete eingelesen werden. Die Rückgabe der Auswahl erfolgt dann in einem Feld mehr für die ID des gewählten Datensatzes (der Adresse). Damit stets die Adresse als String vorliegt sowie zusätzlich die ID, wird ein Behavior eingebunden, welches die jeweils fehlenden Daten afterFind() und beforeSave() einliest.

Hier ergibt sich nun ein Problem, an dem ich eine Weile geknabbert habe:

In der afterFind-Methode muss die Adresse z.B. des Finanzamts eingelesen werden. Das führt unweigerlich zu einer Endlosschleife, weil nach dem Finden des Finanzamt-Datensatzes natürlich afterFind() wieder aufgerufen wird.

Hier die triviale Abhilfe:

Ich erzeuge eine neue Model-Klasse, welche auf die gleiche Tabelle zeigt und ermittle die Adresse über eine Model-Instanz dieser Klasse, in der ich ausschließlich den Tabellennamen angebe:

[i]class CrmStammdatenOnly extends CActiveRecord

{

/**


 * @return string the associated database table name


 */


public function tableName()


{


	return 'crm_stammdaten';


}

}[/i]

So kann ich die find()-Methode auf die Tabelle ausführen, ohne das ein Zugriff auf das Behavior und damit die

afterFind()-Methode des eigentlichen CActiveRecord-Models stattfindet.

Hi, willkommen im Forum. Ist glaube ich mal völlig in Ordnung hier auch Tipps zu posten.

Dein Problemfall kann sicher auch mit einem Szenario gelöst werden (in Kombination mit CActiveRecordBehavior::$enabled).

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]