Wunsch: Keep It Simple in GenericDB oder: Doku?

Fragen zur Installation von CONTENIDO 4.9? Probleme bei der Konfiguration? Hinweise oder Fragen zur Entwicklung des Systemes oder zur Sicherheit?
HerrB
Beiträge: 6935
Registriert: Do 22. Mai 2003, 12:44
Wohnort: Berlin
Kontaktdaten:

Wunsch: Keep It Simple in GenericDB oder: Doku?

Beitrag von HerrB » Di 26. Okt 2004, 17:25

Liebe 4fb-Entwickler,

mit Sicherheit habe ich noch nicht alle Geheimnisse entdeckt und verstanden und vielleicht fehlt doch ein Stück Doku... (also nicht hauen :wink: ):

Ich habe eine Bitte: "Keep it simple" in der GenericDB... Ich habe danach gesucht, aber (noch) keinen Weg gefunden, mit der GenericDB "freie" Abfragen zu gestalten.

Vorschlag:
Ich würde mir entweder eine "FreeSelect"-Methode in der genericdb wünschen (na ja, oder so etwas wie die selectfrom-Methode aus meinem Thread zum Newsletter) oder eine FreeQuery-Methode, bei der man mit AddTable, AddWhere usw. die Elemente zusammenfügt, jedoch keine "Intelligenz" integriert ist (analog select-Methode); man muss die Tabellen und die PrimaryKeys der "fremden" Klassen halt selbst angeben. Bei AddWhere müsste die "Filz"-Funktion deaktivierbar sein.

Als Erläuterung ein (langes) Beispiel:
Tabelle M: Members, PrimaryKey: idM
Tabelle G: Groups, PrimaryKey: idG
Tabelle MG: Members in groups, PrimaryKey: idMG

Folgendes SQL-Statement soll erzeugt werden:

Code: Alles auswählen

"SELECT DISTINCT M.idM FROM M, G, MG WHERE 
M.idclient = '".$client ."' AND M.idlang = '" . $lang . "' AND M.deactivated = '0' AND M.confirmed = '1' AND 
M.idM = MG.idM AND MG.idG = G.idG AND G.defaultgroup = '1' AND G.idclient = '" . $client . "' AND G.idlang = '".$lang."'"
Zur schönen Objektorientierung gibt es eine Klasse für Members (MCollection) und Groups inkl. GroupMembers (GCollection und MGCollection) - d.h. zwei Klassendateien.

IMHO stehen zwei Abfrage-Methoden zur Verfügung: select und query.

SELECT:
Mit <CollectionClass>.Select kann ein fast beliebiges SQL-Statement übergeben werden. Automatisch (und darauf hat man IMHO keinen Einfluss) wird die ID der Klasse als Feld ausgewählt.

Aus <CollectionClass>.Select ("A = B","A") wird "SELECT <PrimaryKey> FROM <ClassTable> WHERE A = B ORDER BY A".

Mit Select ist jedoch kein DISTINCT oder eine Verknüpfung/Join von Tabellen möglich, da keine Möglichkeit besteht, Tabellen im FROM-Bereich anzugeben.

Für die Ausführung der Abfrage muss die <CollectionClass> geladen werden.

QUERY:
Die Funktion ist atemberaubend...Theoretisch ist damit eine flexible Abfrage möglich, aber...

So würde es funktionieren:
Am obigen Beispiel sind drei Tabellen beteiligt. Für jede Tabelle muss eine Collection-Class definiert werden (siehe z.B. class.frontend.groups.php und class.frontend.users.php).

Der Konstruktor jeder Klasse muss einen Verweis auf die ItemClass enthalten:

Code: Alles auswählen

class GCollection extends ItemCollection {	
	function GCollection()
	{
		global $cfg;
		parent::ItemCollection($cfg["tab"]["some_groups"], "idg");
 		$this->_setItemClass("G");
Der Konstruktor der MembersInGroup-(MG)-Klasse muss zusätzlich die Information über die Zusammenhänge zwischen der Members- und Groups-Klasse erhalten:

Code: Alles auswählen

class MGCollection extends ItemCollection {
	function MGCollection()
	{
		global $cfg;
		parent::ItemCollection($cfg["tab"]["some_groupmembers"], "idmg");
		$this->_setJoinPartner ('GCollection');
		$this->_setJoinPartner ('MCollection');
		$this->_setItemClass("MG");
"Schon" kann man mit

Code: Alles auswählen

$groupmembers = new MGCollection;
$groupmembers->link('MCollection');
$groupmembers->link('GCollection');
$groupmembers->setWhere('MCollection.idclient',$client);
$groupmembers->setWhere('MCollection.idlang',$lang);
$groupmembers->setWhere('MCollection.deactivated','0');
$groupmembers->setWhere('MCollection.confirmed','1');
$groupmembers->setWhere('GCollection.defaultgroup','1');
$groupmembers->setWhere('GCollection.idclient',$client);
$groupmembers->setWhere('GCollection.idlang',$lang);
$groupmembers->query();
alle Mitglieder ermitteln, die in der Standard-Gruppe enthalten sind. Fast, zumindest, denn daraus entsteht das folgende SQL:

Code: Alles auswählen

"SELECT MCollection.idM, GCollection.idG, MGCollection.idMG FROM M AS MCollection, G AS GCollection, MG AS MGCollection WHERE 
MCollection.idclient = '<$client>' AND MCollection.idlang = '<$lang>' AND
MCollection.deactivated = '0' AND MCollection.confirmed = '1' AND 
MCollection.idM = MGCollection.idM AND MGCollection.idG = GCollection.idG AND
GCollection.defaultgroup = '1' AND GCollection.idclient = '<$client>' AND GCollection.idlang = '<$lang>'"
(Tatsächlich sind es JOINs, nicht MCollection.idM = MGCollection.idM, dient nur als Beispiel)

Man erhält also die PrimaryKeys von allen drei Tabellen. Außerdem handelt es sich um das MembersInGroup-Objekt und eigentlich sollte es ein Members-Objekt werden, damit man via $member->get("name"); darauf zugreifen könnte.

An sich kein Problem: Mit $member = $groupmembers->fetchObject("MCollection"); wird aus MCollection der PrimaryKey ermittelt und damit das Member-Objekt mit dem gewünschten Key zurückgegeben. Aber das kostet natürlich zusätzlich Zeit.

Damit die Abfrage ausgeführt werden kann, muss jede beteiligte Klasse geladen werden. Ein Distinct ist (bisher) nicht möglich. Eine Verwendung (ja, und das habe ich gerade gebraucht... :wink: ) des IN-Statements dürfte nicht möglich sein (mit $restriction = "'" . $this->_itemClassInstance->_inFilter($item["restriction"]) . "'"; werden in der genericdb die Einträge "gefilzt". Bei einem Konstrukt á la setWhere ("A","['a','b','c']","IN") für "WHERE A IN ['a','b','c']" würden IMHO die Hochkommata verloren gehen).

Meine Meinung:
Toller Code, gute Idee, aber das Ganze ist recht kompliziert und erleichtert wenig die Arbeit: Das SQL-Statement muss ich praktisch schon im Geiste konstruiert haben, sonst kann ich die Bedingungen und Verknüpfungen nicht richtig setzen.

Zusätzlich muss ich nun wissen, wie das System intern funktioniert, damit ich z.B. die Felder richtig benenne ($groupmembers->setWhere('GCollection.idlang',$lang); ). Jede beteiligte Klasse wird geladen. Die Flexibilität, die SQL bietet, wird u.U. begrenzt.

Ich komme an das gewünschte Element nur über eine weitere Zeile, die wieder kreuz und quer Informationen ermittelt, die der Programmierer schon hat (hier: Name des PrimaryKeys der Klasse).

Abfragen á la "SELECT M.idM FROM M, F WHERE M.email = F.email" sind IMHO nicht möglich, da es keine Klasse gibt, die M und F via "email" verbindet/verbinden kann (hey, da gibt es sicherlich einen Trick, den ich noch nicht gefunden habe...)

Mein Vorschlag? Siehe oben.

Jetzt hoffe ich, dass ich nicht zu viel Murks erzählt habe ...

Gruß
HerrB

SkyBlader
Beiträge: 303
Registriert: Do 18. Mär 2004, 16:23
Wohnort: Ratingen / NRW
Kontaktdaten:

Beitrag von SkyBlader » Di 26. Okt 2004, 20:26

uf .. man merkt du hast dich mit der genericdb schon richtig auseinandergesetzt .... damit hab cih grad erst angefangen ( bis jetzt keine notwendigkeit gesehen bzw. zeit gehabt. )

Aber das was du das schreibst, läßt sich für mich atm zumindestens, dein wunsch verstehen.

Gruß

Stefan
Contenido 4.4.4/4.4.5 - Contenido 4.5.x

Module:sn_multinav für 4.5.x

timo
Beiträge: 6284
Registriert: Do 15. Mai 2003, 18:32
Wohnort: Da findet ihr mich nie!
Kontaktdaten:

Beitrag von timo » Di 26. Okt 2004, 21:37

ja, das Problem ist, daß die GenericDB so konzeptioniert wurde, daß eine Zeile gleich einem Objekt ist -> deshalb sind komplett freie Selects auch nicht möglich...sondern es soll eher so sein, daß du pro Datenbankobjekt auch eine Tabelle hast und genau das abgebildet wird.

mit den neuen query- und link-Methoden lassen sich dann Objekte verknüpfen, aber so sachen wie z.b. distinct funktionieren dann natürlich nicht mehr ;)

Da das ganze aber noch in der Testphase ist (mit query und link), wird eine Doku erst später kommen. Ein Kollege hat mal bei den Frontendusers was eingebaut, d.h. wenn du in die Klassen für die Frontendusers schaust, siehst du, wie da Dinge verknüpft werden (ist eigentlich recht angenehm, da du auch über mehrere Objekte joins machen kannst -> auch über mehrere Stufen, die Beziehungen werden dann auch automatisch aufgelöst). Aber wie gesagt ist das erstmal Testphase.

HerrB
Beiträge: 6935
Registriert: Do 22. Mai 2003, 12:44
Wohnort: Berlin
Kontaktdaten:

Beitrag von HerrB » Mi 27. Okt 2004, 18:10

Danke für die Info. Ja, das habe ich schon gesehen (erst mit dem Beispiel aus FrontendUsers habe ich das Prinzip überhaupt erst verstanden).

Es basiert - wie gesagt, wenn ich es richtig verstanden habe - darauf, dass die Tabellen über den PrimaryKey der Klasse verknüpft werden (deswegen muss auch jede beteiligte Klasse einmal geladen werden).
Abfragen á la "SELECT M.idM FROM M, F WHERE M.email = F.email" sind IMHO nicht möglich, da es keine Klasse gibt, die M und F via "email" verbindet/verbinden kann.
Manchmal muss es halt nicht immer der PrimaryKey sein...

Vielleicht gibt es da ja doch noch diesen Trick, wie man das mit der genericdb realisiert. Oder sowas wie SELECT DISTINCT M.idM FROM M, MG, G WHERE M.idM = MG.idM AND MG.idG = G.idG AND G.idG IN ('2','5','7') ("alle Empfänger aus den Gruppen x,y,z").

Das mit den Klassen (die zu verwendende Klasse ist nicht beliebig, sondern ergibt sich aus der gewünschten Abfrage) ist halt für mich irgendwie ungünstig, da ich - bei drei unterschiedlichen Abfragen - doch immer als Ergebnis auf das Member-Objekt zugreifen muss (und die Verwendung von fetchObjekt die Sache nicht gerade beschleunigt).

Ich harre natürlich der Entwicklung - bis dahin habt ihr hoffentlich nix gegen "selectfrom" (siehe Newsletter-Entwicklungs-Thread) :wink:

Gruß
HerrB

timo
Beiträge: 6284
Registriert: Do 15. Mai 2003, 18:32
Wohnort: Da findet ihr mich nie!
Kontaktdaten:

Beitrag von timo » Mi 27. Okt 2004, 20:52

Ich glaube, da gibt es ein Missverständnis ;)

Die GenericDB ist kein generischer "Datenbankabfrager", sondern ein "Objekt-Holer". Da eine Objekthierarchie *immer* über den Primary Key abgebildet wird (bzw werden sollte), gibt es keinen Grund, andere Felder per Join zu verbinden! ;)

Ich weiß, daß es über fetchObject langsamer ist, aber schneller wird es sicherlich nicht, und die Vorteile sind enorm.

emergence
Beiträge: 10644
Registriert: Mo 28. Jul 2003, 12:49
Wohnort: Austria
Kontaktdaten:

Beitrag von emergence » Do 28. Okt 2004, 05:52

timo hat geschrieben:Die GenericDB ist kein generischer "Datenbankabfrager", sondern ein "Objekt-Holer".
das ist ne wirklich wichtige information ;-)
sollte man am beginn der class.genericdb.php vielleicht ergänzen...
*** make your own tools (wishlist :: thx)

HerrB
Beiträge: 6935
Registriert: Do 22. Mai 2003, 12:44
Wohnort: Berlin
Kontaktdaten:

Beitrag von HerrB » Fr 29. Okt 2004, 12:47

Ähhm...gut, da trifft mich jetzt wohl das mangelnde Wissen/Verständnis: Und wie erstellt man dann eine beliebige Abfrage, auf deren Ergebnis man aus dem Collection-Objekt zugreifen kann?
Ich weiß, daß es über fetchObject langsamer ist, aber schneller wird es sicherlich nicht, und die Vorteile sind enorm.
Da ist nicht mein eigentliches Problem; mein Problem ist, dass ich für unterschiedliche Abfragen unterschiedliche Objekte erhalte. Aber mir kommt da gerade eine Idee, das probiere ich mal aus...

Stay tuned... :wink:

Gruß
HerrB

timo
Beiträge: 6284
Registriert: Do 15. Mai 2003, 18:32
Wohnort: Da findet ihr mich nie!
Kontaktdaten:

Beitrag von timo » Fr 29. Okt 2004, 13:42

HerrB hat geschrieben:Ähhm...gut, da trifft mich jetzt wohl das mangelnde Wissen/Verständnis: Und wie erstellt man dann eine beliebige Abfrage, auf deren Ergebnis man aus dem Collection-Objekt zugreifen kann?


Okay, ich geb zu, das ist ein wenig schwierig zu verstehen ;) Joins mit der GenericDB gehen nur über Objekte, nicht über Freiformdaten. d.h. du brauchst mindestens ein Objekt (also Klassen, die von der GenericDB ableiten), damit du Joins bauen kannst, die du auch zurückbekommst. Das Ziel ist, daß alles, was es in Contenido gibt, als Objekte abgebildet werden, sodaß man auch schön brav joins machen kann ;)

HerrB
Beiträge: 6935
Registriert: Do 22. Mai 2003, 12:44
Wohnort: Berlin
Kontaktdaten:

Beitrag von HerrB » Fr 29. Okt 2004, 14:55

Das Ziel ist, daß alles, was es in Contenido gibt, als Objekte abgebildet werden, sodaß man auch schön brav joins machen kann
Habe ich ja so weit verstanden (finde ich ja auch gut) - nur, was macht man, wenn für den Join kein PrimaryKey verwendet kann (e.g. SELECT A.name FROM A, B WHERE A.email = B.email)?

Mal abgesehen von Tricks (e.g. definiere Dummy-Klasses mit PrimaryKey email) fällt mir da so langsam nix mehr ein...

Und die Idee von vorhin muss ich noch weiter testen, die will noch nicht...

Gruß
HerrB

timo
Beiträge: 6284
Registriert: Do 15. Mai 2003, 18:32
Wohnort: Da findet ihr mich nie!
Kontaktdaten:

Beitrag von timo » Fr 29. Okt 2004, 18:29

achso, wenn du Daten hast, die z.b. zweimal in einer Tabelle vorkommen? Guter Gedanke, das Problem hatte ich bisher nicht...aber ich weiß auch nicht, wie man es anders implementieren könnte, außer über eine Zuweisungstabelle...ich werd mal drüber nachdenken!

HerrB
Beiträge: 6935
Registriert: Do 22. Mai 2003, 12:44
Wohnort: Berlin
Kontaktdaten:

Beitrag von HerrB » Fr 29. Okt 2004, 22:10

Konkretes Beispiel: Ich arbeite am Newsletter-Empfänger-Frontend-User-Link, die verknüpfende, eindeutige ID ist die E-Mail-Adresse...

Aber ich mache da auch nochmal einen Vorschlag, wie ich mir das denke.

Gruß
HerrB

timo
Beiträge: 6284
Registriert: Do 15. Mai 2003, 18:32
Wohnort: Da findet ihr mich nie!
Kontaktdaten:

Beitrag von timo » Sa 30. Okt 2004, 17:08

achso... ;)

das ist noch etwas, was wir anders machen: bei uns gibt es *nur* ein int-Feld als PK, niemals einen Text, auch wenn das ab und zu unnötig erscheint

HerrB
Beiträge: 6935
Registriert: Do 22. Mai 2003, 12:44
Wohnort: Berlin
Kontaktdaten:

Beitrag von HerrB » Mi 24. Nov 2004, 13:35

Mal ein Update (nur zur Info):

Die Methode next() ist in der genericdb ist im CVS so definiert:

Code: Alles auswählen

function next()
{	
	if ($this->_mode == "manual")
	{
    		if ($this->db->next_record())
		{
			return $this->loadItem($this->db->f($this->primaryKey));
		} else {
 			return false;
		}
	} else {
		if ($this->db->next_record())
		{
			return $this->loadItem($this->db->f($this->primaryKey));
		} else {
			return false;
		}
	}
}
Tja, das lässt sich auch kürzer schreiben (es gibt - wenn ich nicht Tomaten auf den Augen habe - keinen Unterschied zwischen _mode == "manual" und _mode != "manual"):

Code: Alles auswählen

function next()
{	
	if ($this->db->next_record())
	{
		return $this->loadItem($this->db->f($this->primaryKey));
	} else {
		return false;
	}
}
Ich denke, dass da nochmal was kommen soll/sollte (Unterschied _mode="manual" und _mode="automatic"), deswegen würde ich es jetzt zunächst nicht für einen Bug halten.

Grundsätzlich dürfte diese next()-Methode aber die Methode addResultField häufig überflüssig machen:
1. Es seien entsprechende Klassen und Verbindungen definiert
2. Mit den entsprechenden Methoden sei für query() das Select-Statement definiert (und die gewünschten Ergebnisfelder mit addResultField ergänzt)
3. Abfrage liefere Ergebnisse

Wird nun $object->next() aufgerufen, wird die loadItem-Methode der Objekt-Klasse aufgerufen. Diese wiederum ist häufig (d.h. wo ich sie bisher gefunden habe) mit

Code: Alles auswählen

function loadItem ($itemID)
{
	$item = new FrontendGroup();
	$item->loadByPrimaryKey($itemID);
	return ($item);
}
definiert.

loadByPrimaryKey macht hauptsächlich

Code: Alles auswählen

$success = $this->loadBy($this->primaryKey, $value);
Die wichtige Komponente von loadBy steht am Anfang:

Code: Alles auswählen

function loadBy($field, $value)
{	    
	/* SQL-Statement to select by field */
	$sql = "SELECT * FROM ".
			$this->table
			." WHERE ".$field." = '".$value."'";
...
Tja, und das SELECT * FROM ... eliminiert alle mit AddResultField zur Abfrage hinzugefügten Felder. D.h. man muss, damit man AddResultField überhaupt nutzen kann, die loadItem-Methode der Klasse geeignet definieren (wie auch immer man das machen müsste...).

Natürlich nur, wenn ich alles richtig verstanden habe...

Nur zur Info. Falls mal jemand über AddResultField stolpert. :wink:

Gruß
HerrB

HerrB
Beiträge: 6935
Registriert: Do 22. Mai 2003, 12:44
Wohnort: Berlin
Kontaktdaten:

Beitrag von HerrB » Mi 24. Nov 2004, 13:40

das ist noch etwas, was wir anders machen: bei uns gibt es *nur* ein int-Feld als PK, niemals einen Text, auch wenn das ab und zu unnötig erscheint
Ja, so würde ich auch immer Datenbanken designen. Nur bei meinem Problem nützt dat nix (aber danke für die Info), da das verbindene Element nach wie vor die E-Mail-Adresse auf der einen (Empfänger) und das Namensfeld (in welches die E-Mail-Adresse eingetragen wird) auf der anderen Seite (Frontend-User) ist...

Aber mit "meiner" flexSelect-Methode (vorher: selectfrom) sollte ich das mit wenig Aufwand hinkriegen.

Gruß
HerrB

timo
Beiträge: 6284
Registriert: Do 15. Mai 2003, 18:32
Wohnort: Da findet ihr mich nie!
Kontaktdaten:

Beitrag von timo » Mi 24. Nov 2004, 15:13

HerrB hat geschrieben: Ich denke, dass da nochmal was kommen soll/sollte (Unterschied _mode="manual" und _mode="automatic"), deswegen würde ich es jetzt zunächst nicht für einen Bug halten.
Ja, ich würde es so lassen, irgendwas hatte ich mal geplant, damit das auch berücksichtigt wird.
Grundsätzlich dürfte diese next()-Methode aber die Methode addResultField häufig überflüssig machen:
1. Es seien entsprechende Klassen und Verbindungen definiert
2. Mit den entsprechenden Methoden sei für query() das Select-Statement definiert (und die gewünschten Ergebnisfelder mit addResultField ergänzt)
3. Abfrage liefere Ergebnisse
Naja, das ist teilweise historisch bedingt. addResultField ist dafür da, um bei einer Abfrage (denke an eine Menge "n" an Zeilen) zusätzliche Spalten abzufragen und diese auch nur *rein* für Abfragezwecke zu verwenden (also für die fetchArray, fetchObject-Methoden). Ein Objekt benötigt immer alle Felder, damit es vernünftig arbeiten kann. Ein $collection->next ist also nicht nötig, wenn man nur eine Abfrage macht. Da die Klasse aber nicht wissen kann, ob man jetzt Objekte mit next iteriert, muß der PK + n Felder abgefragt werden.
Tja, und das SELECT * FROM ... eliminiert alle mit AddResultField zur Abfrage hinzugefügten Felder. D.h. man muss, damit man AddResultField überhaupt nutzen kann, die loadItem-Methode der Klasse geeignet definieren (wie auch immer man das machen müsste...).
Ähm dafür ist ja die Methode query da -> verwendet man die "neuen" Funktionen (query, setWhere, addResultField), ist alles okay, verwendet man nur die "alten" Funktionen (select), funktioniert es natürlich nicht.

Hope that helps ;)

Antworten