T-Rex: Datencontainer für einen Join

Hallo,

da hätte ich mal ein kleines Problemchen. Ich hab da eine Datenbank. Und weil ich OOP ganz nett finde hab ich zu jeder Tabelle eine Klasse z.B. cUser. cUser ist eine Unterklasse einer anderen Klasse, welche die Datenbank Verbindung erleichtern soll ... ist eigentlich nicht weiter wichtig. Für eine Datenbank abfrage habe ich ein Objekt. Mache ich eine Datenbank abfrage so bekomme ich Objekte der Klasse cUser zurück.

Als Beispiel:

$objUser = $objSQL->query("query","cUser"); // gib user nr. 10 zurück als cUser  
$objUser->setName("Hans Dampf");  
echo $objUser->getStrasse();  
$objUser->save();  
$objUser->delete();  

Wie man sieht besteht cUser vor allem aus getter und setter. Meistens pro Tabellenfeld ein setter und getter. Dazu kommen noch ein paar Methoden von der Oberklasse wie save() oder delete().
Für jede Tabelle hab ich solche Datencontainer.

Mein Problem ist jetzt, wenn ich einen Join mache bräuchte ich auch auf der PHP schiene ein "Join" zweier Datencontainer. Beim Beispiel von oben:
Wenn ich einen Join User -> Userrechnung mache, aber den Datencontainer "cUser" benutze, habe ich keine Methode um an die Rechnungsnr. zu kommen. Andersrum, wenn ich cRechnung benutze komme ich nicht an den Usernamen. Außer natürlich man erweitert die Datencontainer. Das möchte ich jedoch nicht :D.

Problem klar?
Lösung?

Danke
Gruß
T-Rex

  1. Hi!

    da hätte ich mal ein kleines Problemchen. Ich hab da eine Datenbank. Und weil ich OOP ganz nett finde hab ich zu jeder Tabelle eine Klasse z.B. cUser. cUser ist eine Unterklasse einer anderen Klasse, welche die Datenbank Verbindung erleichtern soll ... ist eigentlich nicht weiter wichtig. Für eine Datenbank abfrage habe ich ein Objekt. Mache ich eine Datenbank abfrage so bekomme ich Objekte der Klasse cUser zurück.

    Datenbankverbindung und Datenbankabfrage sind jeweils ein zusammengeschriebenes Wort. Zur Not mit Bindestrich geschrieben: Datenbank-Verbindung und Datenbank-Abfrage, aber bitte nicht auseinander.

    Es ist übrigens nicht immer sinnvoll, eine 1:1-Abbildung zwischen Tabellenstrukturen in einem relationalen DBMS und Klassen im Programm zu erstellen. Ein RDBMS hat gewisse Zwänge, eigentlich zusammenhängende Daten auf mehrere Tabellen verteilen zu müssen. Man kann nicht direkt wie bei der OOP modellieren, dass eine Eigenschaft auf ein anderes Objekt verweist (Parent beispielsweise) oder eine Liste anderer Objekte ist (Children). Dazu verwendet man bei einem RDBMS Verweise mit Schlüsselwerten.

    Mein Problem ist jetzt, wenn ich einen Join mache bräuchte ich auch auf der PHP schiene ein "Join" zweier Datencontainer. Beim Beispiel von oben:
    Wenn ich einen Join User -> Userrechnung mache, aber den Datencontainer "cUser" benutze, habe ich keine Methode um an die Rechnungsnr. zu kommen. Andersrum, wenn ich cRechnung benutze komme ich nicht an den Usernamen. Außer natürlich man erweitert die Datencontainer. Das möchte ich jedoch nicht :D.

    Du siehst, dass dein Modell nicht geeignet ist, wenn du wie bei den Tabellen in deinen Klassen nur Eigenschaften für die Schlüsselwerte stehen hast und nicht mit ihnen auf direkte Weise andere Objekte referenziert werden. Denn das Suchen konkreter Objekte über ihre Schlüsselwerte ist zwar möglich aber aufwendig.

    Eine einfache Möglichkeit wäre, die starre Verbindung zwischen Tabelle, Abfrage und Klassen zu lösen. Dann kannst du Abfragen nach Bedarf erstellen, beliebig viele Tabellen dafür verwenden und füllst deine Objekte nach Bedarf und vor allem passend zu den Anforderungen.

    Und wenn du mal schauen willst, wie andere Frameworks das lösen, such nach Eager Loading und Lazy Loading. Sowas lässt sich prinzipiell auch mit PHP und etwas Magie bewerkstelligen.

    Lo!

    1. Es ist übrigens nicht immer sinnvoll, eine 1:1-Abbildung zwischen Tabellenstrukturen in einem relationalen DBMS und Klassen im Programm zu erstellen. Ein RDBMS hat gewisse Zwänge, eigentlich zusammenhängende Daten auf mehrere Tabellen verteilen zu müssen. Man kann nicht direkt wie bei der OOP modellieren, dass eine Eigenschaft auf ein anderes Objekt verweist (Parent beispielsweise) oder eine Liste anderer Objekte ist (Children). Dazu verwendet man bei einem RDBMS Verweise mit Schlüsselwerten.

      Die Problematik bezüglich Objekten und RDBMS waren mir von vornherein klar. Meine Sicht auf die Dinge ist auch anders als du beschreibst. Bei deiner Beschreibung stehen die Objekte im vordergrund, welche man versucht auf in eine Datenbank zu pressen. Ich hingegen möchte die Tabellen der Datenbank in Objecte pressen :D.
      Für deinen Ansatz gibt es ja diverse Frameworks. Die erkaufen sich aber die Effektivität, welche man beim programmieren hat durch eine schlechtere Datenbank-Anbindung (;)). Ich hab sogar ein Framework gesehen, welches das Objekt serialisiert und einfach in eine text Spalte stopft.

      Du siehst, dass dein Modell nicht geeignet ist, wenn du wie bei den Tabellen in deinen Klassen nur Eigenschaften für die Schlüsselwerte stehen hast und nicht mit ihnen auf direkte Weise andere Objekte referenziert werden. Denn das Suchen konkreter Objekte über ihre Schlüsselwerte ist zwar möglich aber aufwendig.

      Genrell bin ich sehr zufrieden mit meinem Modell. Es gibt halt jetzt ein kleines Problem. Ich möchte die generelle Idee nicht wegen eines Problems über Bord werfen. Sollte sich das Problem als unüberwindbar heraussstellen werde ich dir recht geben. Aber genau deshalb suche ich ja Rat.

      Eine einfache Möglichkeit wäre, die starre Verbindung zwischen Tabelle, Abfrage und Klassen zu lösen. Dann kannst du Abfragen nach Bedarf erstellen, beliebig viele Tabellen dafür verwenden und füllst deine Objekte nach Bedarf und vor allem passend zu den Anforderungen.

      Dass kann ich mir im Moment nicht vorstellen...

      Und wenn du mal schauen willst, wie andere Frameworks das lösen, such nach Eager Loading und Lazy Loading. Sowas lässt sich prinzipiell auch mit PHP und etwas Magie bewerkstelligen.

      ...aber hier steckt eventuell die Lösung.
      Werde aber erst am Montag dazu kommen mir das mal konzentriert durch zu lesen. Heute wird erstmal Geburtstag gefeiert. Da ich die letzten 3 Monate jeden Tag vor dem Rechner saß und Programmiert hab wie ein Irrer muss mana uch mal abschalten!

      Danke für deine Ideen.

      Gruß
      T-Rex

      1. Hi!

        Die Problematik bezüglich Objekten und RDBMS waren mir von vornherein klar. Meine Sicht auf die Dinge ist auch anders als du beschreibst. Bei deiner Beschreibung stehen die Objekte im vordergrund, welche man versucht auf in eine Datenbank zu pressen. Ich hingegen möchte die Tabellen der Datenbank in Objecte pressen :D.

        Man muss ja nicht unbedingt Gewalt anwenden. Es finden sich garantiert auch Lösungen, die ohne zu pressen geschmeidig miteinander werkeln. So wie du an die Sache rangehst, die Struktur der Tabellen abzubilden, empfiehlt es sich eher für Datenbankverwaltungswerkzeuge als für Anwendungen, die die Datenbank lediglich als Speicher für Daten verwenden wollen.

        Für deinen Ansatz gibt es ja diverse Frameworks. Die erkaufen sich aber die Effektivität, welche man beim programmieren hat durch eine schlechtere Datenbank-Anbindung (;)).

        Die Frameworks können nur mehr oder weniger eine allgemeine Lösung bieten, weswegen sie auf allgemeinem Weg versuchen müssen, das Problem zu lösen. Und das muss ja auch zu einem großen Prozentsatz reichen, sonst hätte das Prinzip nicht so eine große Verbreitung/Akzeptanz. Einige bieten auch die Möglichkeit, Views oder Stored Procedures anzusprechen, und damit kommt man der Individualität schon einen großen Schritt näher. Du musst ja aber keines dieser Frameworks einsetzen.

        Eine einfache Möglichkeit wäre, die starre Verbindung zwischen Tabelle, Abfrage und Klassen zu lösen. Dann kannst du Abfragen nach Bedarf erstellen, beliebig viele Tabellen dafür verwenden und füllst deine Objekte nach Bedarf und vor allem passend zu den Anforderungen.
        Dass kann ich mir im Moment nicht vorstellen...

        Nicht unbedingt mit einem allgemein konzipierten Framework. Es wäre ja auch möglich, dass du nur eine allgemeine Datenbank-Zugriffsschicht nimmst, die grundlegende RUDI/CRUD-Operationen ausführen kann. Die nächste Schicht hat als AUfgabe, zwischen deinen Objekten und der Persistierung zu vermitteln. Und die kannst du individuell lösen. Sprich: für jedes individuelle Problem erstellst du ein individuelles Statement und bekommst die Daten genauso, wie du es für den Moment benötigst. Wenn sich mal was an deiner Tabellenstruktur ändert, musst du theoretisch nur dieses eine Statement umschreiben, und alles andere läuft weiter. Praktisch wird es wohl eher so sein, dass diese Änderung aufgrund fachlicher Anforderungen erfolgen soll und man diese auch im restlichen Programm berücksichtigen muss.

        Wenn du allerdings mit deinem Model mal was an einer Tabelle änderst, kommst du im Prinzip gar nicht daran vorbei, alle Verwendungen im Programm ebenfalls anzupassen.

        Und wenn du mal schauen willst, wie andere Frameworks das lösen, such nach Eager Loading und Lazy Loading. Sowas lässt sich prinzipiell auch mit PHP und etwas Magie bewerkstelligen.
        ...aber hier steckt eventuell die Lösung.

        Im Umfeld dieser beiden Muster wirst du auch darauf treffen, wie im Allgemeinen die Beziehung zwischen Daten und Persistierung aufgebaut ist.

        Lo!

  2. Mahlzeit T-Rex,

    Mein Problem ist jetzt, wenn ich einen Join mache bräuchte ich auch auf der PHP schiene ein "Join" zweier Datencontainer.

    Herzlichen Glückwunsch, Du hast soeben das prinzipielle Problem einer jeden Datenbankabstraktionsschicht (AKA "ORM") erkannt.

    Problem klar?

    Ja.

    Lösung?

    Z.B. zusätzlich zu den rein "technischen" Objekten, die direkt Tabellen und darin enthaltene Datensätze repräsentieren, weitere Objekte, die abstrakte "Business Logic" enthalten (und ggf. Ableitungen der technischen Objekte sind), definieren.

    MfG,
    EKKi

    --
    sh:( fo:| ch:? rl:( br:> n4:~ ie:% mo:} va:) de:] zu:) fl:{ ss:) ls:& js:|
    1. Z.B. zusätzlich zu den rein "technischen" Objekten, die direkt Tabellen und darin enthaltene Datensätze repräsentieren, weitere Objekte, die abstrakte "Business Logic" enthalten (und ggf. Ableitungen der technischen Objekte sind), definieren.

      Ja genau sowas hat mir auch vorgeschwebt. Am liebsten würde ich von mehreren Klassen erben lassen.

      class cUserRechnung extends cUser extends cRechnung

      Aber das geht ja nicht (oder gibts da ein workaround?). Alternativ könnte ich
      class cUserRechnung extends cUser
      nur cUser vererben und die fehlenden Methoden nochmals ergänzen. Dann hätte ich aber redundanten code.

      Was mir noch als Notlösung eingefallen wäre, wäre eine Generierung von PHP code. Der öffnet die Datei class.user und class.rechnung sucht nach den Methoden und baut daraus eine neue class.userrechnung. Ist aber reiner pfusch und ist wahrscheinlich sehr fehleranfällig und kompliziert.

      Mehrfachvererbung in PHP ist glaub ich das Zauberwort...

      Gruß
      T-Rex

      1. Hi,

        Was mir noch als Notlösung eingefallen wäre, wäre eine Generierung von PHP code.

        So ist ORM ja auch in den meisten Frameworks umgesetzt - der benötigte Code zum Zugriff auf Tabellen wird automatisch generiert.
        Allerdings eher auf Klassen-Ebene.

        Der öffnet die Datei class.user und class.rechnung sucht nach den Methoden und baut daraus eine neue class.userrechnung. Ist aber reiner pfusch

        Wenn du so vorgehst, Ja :-)

        Wenn du bestimmte JOINs hast, die du immer wieder brauchst - dann könntest du dafür eine VIEW anlegen.
        Deren Definition kann dann ein Script auslesen, und dazu den passenden PHP-Code für ein ORM-Objekt generieren.

        Wenn du aber für „jede beliebige“ Abfrage, die sich der Entwickler vielleicht irgendwann mal ausdenken könnte, ein entsprechendes Objekt erstellen willst, ist der Ansatz weniger sinnvoll.
        Dann solltest du keine „direkten“ Queries absetzen (lassen), sondern erst mal ein Query-Objekt haben, über das man sich eine Abfrage „zusammenbauen“ kann - schau dir bspw. mal Doctrine's DQL an; Dort genauer die Beispiele, in denen Queries zusammengesetzt werden:

        $q = Doctrine_Query::create()  
            ->select('u.username, p.*')  
            ->from('User u')  
            ->leftJoin('u.Phonenumbers p');
        

        Wenn du die Query auf diese Weise erstellst, dann hast du auch eine Stelle, an der du gleichzeitig die Struktur des Ergebnisses analysieren kannst.
        Damit kannst du dann auch (dynamisch) entsprechende Methoden zum Zugriff auf die Ergebnisse bereit stellen.

        MfG ChrisB

        --
        RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
      2. Hi!

        Mehrfachvererbung in PHP ist glaub ich das Zauberwort...

        Hammwa nich, kriegnwa auch nich rein. Muss anders gehen.

        Lo!

    2. Z.B. zusätzlich zu den rein "technischen" Objekten, die direkt Tabellen und darin enthaltene Datensätze repräsentieren, weitere Objekte, die abstrakte "Business Logic" enthalten (und ggf. Ableitungen der technischen Objekte sind), definieren.

      Wieso? Der ORM in Ruby on Rails vereinigt das doch (letztlich) auch?

      user = User.find(10)
      user.name = 'Hans Dampf'
      puts user.strasse
      user.save
      user.destroy

      Wenn der User jetzt Rechnungen hat, so legt man eine has_many-Assoziation an und kann notieren:

      invoice = user.invoices.first
      puts invoice.id

      Das ist alles, da muss man nicht händisch Query-Objekte erzeugen und irgendwie vermixen. Rails erzeugt unter der Haube natürlich Arel-Queries, aber damit muss man sich nicht beschäftigen.

      Gibts das nicht für PHP?

      Mathias

      1. Hallo Mathias,

        Das ist alles, da muss man nicht händisch Query-Objekte erzeugen und irgendwie vermixen. Rails erzeugt unter der Haube natürlich Arel-Queries, aber damit muss man sich nicht beschäftigen.

        ich hab' reingeschaut. Vorab: ich versteh' kein Ruby. Das laut Doku entstehende SQL sah nicht überzeugend aus. ON-Klausel und JOIN-Klausel werden munter miteinander vertauscht, was bei OUTER-Joins nicht zulässig ist (vermutlich ein Dokufehler).

        ORM gibt's auch für PHP, zum Beispiel Doctrine.

        Freundliche Grüße

        Vinzenz

        1. Das laut Doku entstehende SQL sah nicht überzeugend aus. ON-Klausel und JOIN-Klausel werden munter miteinander vertauscht, was bei OUTER-Joins nicht zulässig ist (vermutlich ein Dokufehler).

          Wobei jetzt? Von welchem Fall redest du?

          Das einfache Beispiel user.invoices erzeugt (beim Zugriff auf ein Element der Liste) ein einfaches »SELECT * FROM invoices WHERE user_id = ?«.

          Mathias

          1. Hallo Mathias,

            Das laut Doku entstehende SQL sah nicht überzeugend aus. ON-Klausel und JOIN-Klausel werden munter miteinander vertauscht, was bei OUTER-Joins nicht zulässig ist (vermutlich ein Dokufehler).

            Wobei jetzt? Von welchem Fall redest du?

            erstes Beispiel der komplexen Joins (hier ein Selfjoin):

            replies = comments.alias  
            comments_with_replies = \  
              comments.join(replies).on(replies[:parent_id].eq(comments[:id]))
            

            => ~~~sql

            SELECT * FROM comments INNER JOIN comments AS comments_2 WHERE comments_2.parent_id = comments.id

              
            klar, hier liegt kein OUTER JOIN vor, aber es verunsichert den Leser, der von der SQL-Schiene kommt.  
            Wie erwähnt vermute ich eher einen Fehler in der Doku.  
              
              
            Freundliche Grüße  
              
            Vinzenz
            
  3. Hey T-Rex du doofian, dass ist doch ganz einfach :D.
    Nagut spaß bei Seite.
    Wie gesagt denke ich ist die Lösung die Mehrfachvererbung. Deshalb habe ich auf der Schiene mal Recherchiert und habe einen netten Eintrag irgendwo in einem englischen Forum gefunden. Den Code dort habe ich ein wenig angepasst und automatisiert. Als erstes hat man eine Klasse welche die Mehrfachvererbung simulieren soll (damit ich mich leichter tue beim erklären möchte ich davon ausgehen dass es eine echte Mehrfachvererbung ist).

    	class cMultiInharitance  
    	{  
    		private $arMethods    = array();  
    		private $arInstances  = array();  
    		private $intId        = 0;  
    		  
    		public function __construct($arClasses)  
    		{  
    			if(!is_array($arClasses))  
    			{  
    				return false;  
    			}  
      
    			foreach ( $arClasses as $strClassName )  
    			{  
    				if(class_exists($strClassName))  
    				{  
    					$this->arInstances[ $strClassName ] = new $strClassName();  
    					//--- Per Reflektion alle Public Methoden raussuchen  
    					$objReflection = new ReflectionClass( $strClassName );  
    					$arPublicProp = $objReflection->getMethods( ReflectionProperty::IS_PUBLIC );  
    					foreach($arPublicProp as $objProp)  
    					{  
    						if( isset($this->arMethods[$objProp->getName()]) )  
    						{  
    							//--- überschreiben der Methode  
    						}  
    						$this->arMethods[ $objProp->getName() ] = $strClassName;  
    					}  
    				}  
    			}  
    		}  
    	  
    		public function __call($strMethod, $arArguments)  
    		{  
    			if( !isset( $this->arMethods[$strMethod] ) )  
    			{  
    				return;  
    			}  
    			  
    			$objClass = $this->arInstances[ $this->arMethods[$strMethod] ];  
    			return call_user_func_array(array($objClass,$strMethod), $arArguments);  
    		}		  
    	}  
    
    

    zur Erklärung komm ich gleich.
    Diese Klasse wird einer anderen Klasse, welche die Mehrfachvererbung erhalten soll vererbt.

    	class cMultiClass extends cMultiInharitance  
    	{  
    		public function __construct()  
    		{  
    			$arClass = array();  
    			$arClass[] = "cClassA";  
    			$arClass[] = "cClassB";  
    			parent::__construct($arClass);  
    		}  
    	}  
    
    

    Die Klassen cClassA und cClassB sind hier nur aus Demonstrationsgründen genannt.
    class cMultiClass extends cMultiInharitance wäre das gleiche wie class cMultiClass extends cClassA extends cClassB.
    Beim initialisieren übergibt man ein Array mit den Klassen welche man Vererben möchte. Diese werden dann in der cMultiInharitance Initialisiert, per Reflektion auseinander genommen und anhand ihrer Public Methods untersucht. Diese werden dann in einem Array gespeichert.
    Ruft man eine Methode von cMultiClass auf, so wird diese ganz einfach wie gewöhnlich aufgerufen. Ruft man eine Methode auf die nicht in cMultiClass implementiert ist, so wird die __call Methode der cMultiInharitance Klasse aufgerufen. Diese wiederum guckt ob sie eine entsprechende Methode der Unterklassen gespeichert hat. Wenn ja wird der aufruf an diese weitergeleiet.

    Das Code dort oben ist auf meine Bedürfnisse angepasst. Es gibt einige enerell Schwachstellen. Es werden z.B. keine Namespaces behandelt. Zudem könnte man eine exception werfen, wenn 2 gleichenamige Methoden in Unterklassen existieren. Außerdem wird gleich mit der Auswertung der Unterklassen angefangen. Dies könnte man verzögert ausführen, wenn man "sicher" ist dass die Klasse angesprochen wird.

    Was zudem nicht funktionieren dürfte ist eine Mehrfachvererbung einer anderen Klasse zu geben. Also:

    class cMultiClass extends cMultiInharitance  
    {  
    }  
      
    class cMultiMultiClass extends cMultiInharitance  
    {  
    	public function __construct()  
    	{  
    		$arClass = array();  
    		$arClass[] = "cMultiClass";  
    		parent::__construct($arClass);  
    	}  
    }
    

    Aber das Konstrukt hab ich nicht getestet, da ich es nicht brauche.
    Ich bin mir zudem sicher das einige viele andere Dinge ebenfalls nicht funktionieren. Wie gesagt hab egoistischer Weise nur mein Problem mit eine wenig Magie ;) gelöst.

    Danke an alle Hinweisgeber.
    Hoffe ich hab die Sachen ein bischen Verständlich beschrieben.

    Gruß
    T-Rex