Zum Inhalt springen

Schwaches Entity (weak entity) in Datensätzen

Erstellt am 4. November 2005 · 29 Antworten · letzte Antwort am 15. Januar 2007 · Seite 1 von 2

Tags: TCA, Frage

BenjaminW ·

Hi!

Ich möchte in Typo3 die folgende (etwas vereinfacht dargestellte) Beziehung abbilden:

Eine Firma hat mehrere Mitarbeiter.
Kein Mitarbeiter ist ohne Firma.
(Diese Art von 1 zu n Beziehung wird auch als Weak entity oder auch "identifizierende Beziehung" bezeichnet)

Wenn ich jetzt ein normales PHP Projekt hätte, wo ich selbst auf die Datenbank zugreife, hätte ich das demnach so gestaltet:

Tabelle Firma mit folgenden Spalten:

fid (Firmen-ID, Primary Key)
Name (Firmenname)

-----------------------------------

Tabelle Mitarbeiter mit folgenden Spalten:

mid (Mitarbeiter-ID, Primary Key)
fid (Firmen-ID, Primary Key!)
Name (Mitarbeiter-Name)

Ich krieg es einfach nicht gebacken, diese (ihr müsst zugeben) nicht gerade so unübliche Beziehung dem lieben Typo3 klarzumachen! Es kennt offenbar nur nicht-identifizierende Datenbankbeziehungen. Jedenfalls fand ich im Kickstarter sowie auch in der Doku zum TCA *keine* Angaben dazu.

Es geht im konkreten Fall um einzelne Veranstaltungen, die mehrere Termine haben. In der Anlegung einer Veranstaltung soll auch die Möglichkeit sein, direkt Termine zu der Veranstaltung hinzuzufügen. Achja: Die Termine müssen in einer separaten Tabelle stehen, also der quick&dirty Weg, einfach die Termine zeilenweise in ein Textfeld zu schreiben, is nich #giggle#

Bitte helft mir #paralyzed#

Gruß,
Benjamin

jenses ·

Dann hast du aber nicht wirklich gesucht 😉
Was du vorhast, ist absolut problemfrei mit TYPO3 umzusetzen.
Schau mal in die TypoCoreAPI, Stichwort foreign_table.

BenjaminW ·
jenses schrieb

Dann hast du aber nicht wirklich gesucht 😉
Was du vorhast, ist absolut problemfrei mit TYPO3 umzusetzen.
Schau mal in die TypoCoreAPI, Stichwort foreign_table.

*gg* ähm ich habe mich da wohl leider falsch ausgedrückt. Eine einfache Liste von Datensätzen einer fremden Tabelle ist das nicht wirklich. Das ist ja kein Problem.

Nennen wir nochmal das Beispiel mit der Firma und den Mitarbeitern:

Ich lege eine Firma an. Im Formular für die Firma, soll nun die Möglichkeit sein, Mitarbeiter anzulegen, die ich der Firma hinzufüge. Soweit kein Ding.

Nun lege ich eine weitere Firma an. Nach dem normalen Verfahren würde ich jetzt in der Mitarbeiter-Selectbox *alle* in der Tabelle vorhandenen Mitarbeiter sehen. Das will ich aber nicht. Sondern ich möchte *nur* die zu einer Firma gehörenen Mitarbeiter sehen. Deswegen heisst das ja auch Weak entity. Der Mitarbeiter kann nicht ohne Firma existieren, weil der FK Bestandteil des Primärschlüssels ist. Und genau so eine Beziehung ist für mein Dafürhalten so einfach nicht möglich.

Im konkreten Beispiel geht es wie gesagt um Veranstaltungen die mehrere Start- und Endzeiten haben. Es macht wenig sinn, in der Liste für die Zeiten nun *alle* Termine aller Veranstaltungen zu sehen. Es muss eine direkte Verknüpfung mit der Veranstaltung sein (sonst wäre das ne M:N Beziehung).

Was auch wichtig ist: Wenn ich nun eine Veranstaltung lösche, sollen die damit verknüpften Zeiten auch gelöscht werden!

Versteht Ihr jetzt mein Problem besser? #paralyzed#

Ich habe wirklich stundenlang gestern gegoogelt. Einige hatten ein ähnliches Problem. Die Lösung die vorgeschlagen wurde war, ein eigenes Modul zu machen, die die DB Arbeit selbst macht. Ich habe halt nur die Hoffnung, dass man das nicht doch anders lösen kann.

Gruß,
Benjamin

woida ·

Du könntest das ganze doch so lösen:
Du legst eine neue Frima an: "Frima1".
Als nächstes legest du einen Mitarbeiter, "Mitarbeiter1" an. In einer Selectbox (im selben Formular) wählst du dann die Firma aus; so ist jeder Mitarbeiter nur in einer Firma.

Nur für das Löschen wirst du dir eine eigene Funktion schreiben müssen.

BenjaminW ·
woida schrieb

Du könntest das ganze doch so lösen:
Du legst eine neue Frima an: "Frima1".
Als nächstes legest du einen Mitarbeiter, "Mitarbeiter1" an. In einer Selectbox (im selben Formular) wählst du dann die Firma aus; so ist jeder Mitarbeiter nur in einer Firma.

Nun, im konkreten Beispiel geht es ja um Start und Endzeiten von Veranstaltungen. o.O

Das würde bedeuten, dass man ja erst ne Veranstaltung anlegen und anschließend separat Termine anlegen muss, die man dann mit einer Veranstaltung verknüpft. Naja, ich denke da ist der Workflow nicht gerade einfach...

woida schrieb

Nur für das Löschen wirst du dir eine eigene Funktion schreiben müssen.

Kann man denn sich irgendwie dazwischenklemmen (z.B. ein Hook), wenn ein Datensatz gelöscht wird?

Stoneage ·

Hallo Benjamin,

Ich programmiere zur Zeit auch eine Veranstaltungsdatenbank, und fand Deine Frage sehr inspirierend. Herzlichen Dank! Ich habe mich sogar gleich drangesetzt und eine Lösung gefunden.

Meine Lösung:

1) in der tabelle veranstaltungen habe ich ein Feld Termine mit einer 1:n Beziehung (genau wie Du beschrieben hast, weak entity) zu der Tabelle tx_termine mit folgender config:

$TCA (Ausschnitt):

   <?php
 	"termine" => Array (		
			"exclude" => 1,		
			"label" => "LLL:EXT:veranstaltungen/locallang_db.php:tx_veranstaltung.termine",		
			"config" => Array (
				"type" => "select",		
				"size" => 10,	
				"minitems" => 0,
				"maxitems" => 20,
				"foreign_table" => "tx_termine",	
				"foreign_table_where" => "AND tx_termine.foreign_uid=###THIS_UID###"
				"wizards" => Array(
					"_PADDING" => 2,
					"_VERTICAL" => 1,
					"add" => Array(
					...
	?>

wichtig ist die zeile: "foreign_table_where" => "AND tx_termine.foreign_uid=###THIS_UID###"

damit hast du schon eine weak entity beziehung. das problem ist nun wie kriege ich die uid von der veranstaltung in die tabelle tx_termine, wenn man aus der veranstaltung einen neuen termin anlegt? das hat etwas gedauert bis bei mir der groschen fiel. zuerst habe ich gedacht das es über das wizard add skript (wizard_add.php) gehen würde, aber das war eine Sackgasse. Nach langem suchen hatte ich dann die idee das feld foreign_uid in der tabelle tx_termine als typ 'user' anzulegen. damit hat es dann geklappt (siehe step 2)

2) in der tabelle tx_termine habe ich das feld foreign_uid als typ 'user' angelegt (fid (Firmen-ID, Primary Key!) in Deiner Tabelle). in der user funktion habe ich dann ein hidden feld mit der aktuellen eltern uid zurückgegeben, so dass beim speichern des ganzen formulars das feld in die tabelle tx_termine abgespeichert wird! es sieht zwar etwas unschön aus, wenn ein hiddenfeld zurückgegeben wird, weil da automatisch ein normales be feld mit label und tabelle drumrum gewrapt wird, aber ich kann damit leben (der kunde hoffentlich auch). als letztes musste ich noch sicherstellen, dass die foreign_uid nicht belegt ist, weil man die Termine auch direkt bearbeiten kann! in diesem fall wird die (existierende) foreign_uid als hidden feld zurückgegeben.

Beispiel:

$TCA:

		<?php
   		'foreign_uid' => Array (
			'exclude' => 1,			
			'config' => Array (
				 'type' => 'user',
				 'userFunc' => 'tx_veranstaltungen_tcaTypeUser->hiddenUID',
			)
		),
		?>

die Klasse:

   <?php
   class tx_veranstaltungen_tcaTypeUser {
 
	function hiddenUID($PA, $fobj) {		
		
		if (empty($PA['row']['foreign_uid'])) {
			# create new date or doc			
			$getParams = explode('&',t3lib_div::_GET('returnUrl'));
			foreach ($getParams as $key) {
				$temp = explode("=",$key);
				$getParams[$temp[0]]=$temp[1];
			}			
			return '<input type="hidden" name="'.$PA['itemFormElName'].'" value="'.$getParams['P[uid]'].'" />';
		} else {
			# edit date or doc
			return '<input type="hidden" name="'.$PA['itemFormElName'].'" value="'.$PA['row']['foreign_uid'].'" />';
		}
		
	}
  }

Anmerkung: die eltern uid hole ich mir von der returnUrl, das ist nicht gerade schön, aber es funktionert!

3) fürs löschen habe ich den Hook processCmdmap_preProcess in der klasse tcemain benutzt. siehe auch tt_news.

Beispiel:

die Registrierung des Hooks in der ext_localconf.php

    <?php
   # Register t3lib_tcemain processCmdmapClass (processCmdmap_preProcess) hook
$TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['0'] = 'EXT:veranstaltungen/pi1/class.tx_tcemain.php:tx_tcemain';
	?>

die Klasse:

   <?php

	class tx_tcemain {
	
		function processCmdmap_preProcess($command, $table, $id, $value, &$pObj) {		
			if ($command == 'delete' && $table == 'tx_termine') {
				# alle Termine dieser Veranstaltung löschen
				$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*','tx_termine','foreign_uid='.$id);			
				$result = array();		
				while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
					$pObj->deleteRecord('tx_termine',$row['uid'], 0);
				}
			}
		} 
    }
	?>

Und es funktioniert! Ich kann aus jeder Veranstaltung heraus mehrere Termine anlegen, die Veranstaltung kennt nur zugehörige Termine, wenn ich eine Veranstaltung lösche, werden alle zugehörigen Termine gelöscht. Perfekt! Getestet auf T3 3.8.0.

Ich hab oft in die T3 Core Api geguckt, und kann das nur empfehlen: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Index.html

Viele Grüße,

T.

steffenk ·

WOW ! 8-)

Das finde ich sehr interessant und ich werde mir das genauer zu Gemüte führen, das ist ja kein Einzelfall für diese Anforderung.

Wird Deine Veranstaltungs-Extension auch public ? **interesse bekunde**

BenjaminW ·
Tetramatrix schrieb

Ich programmiere zur Zeit auch eine Veranstaltungsdatenbank, und fand Deine Frage sehr inspirierend. Herzlichen Dank! Ich habe mich sogar gleich drangesetzt und eine Lösung gefunden.

VIELEN DANK!!! Das freut mich aber sehr.
Ich werds mir gleich anschauen. 😃

Gruß,
Benjamin

BenjaminW ·

Es funktioniert tatsächlich. So ganz will mir aber das nicht gefallen. Erstens habe ich jetzt eine doppelte Referenzierung, das heisst, ich habe in der Tabelle Veranstaltungen einen BLOB mit komma-getrennten IDs (kommt gut bei SELECT Abfragen mit JOINs...) und zusätzlich in der Termine Tabelle noch die direkte Referenzierung zu den Veranstaltungen...
Aber wir kommen dem ganzen schon ein gutes Stück näher.

Kann man sich irgendwie die BLOB-Spalte sparen? Dann hätte ich halt noch das Problem, dass ich nicht in der List-Ansicht die Start und Endzeiten als Übersicht habe.

Der Hook-Code hatte einen kleinen Fehler:

<?php
# Register t3lib_tcemain processCmdmapClass (processCmdmap_preProcess) hook
$TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] = 'EXT:veranstaltungen/pi1/class.tx_tcemain.php:tx_tcemain';
?>

Aus dem ['processCmdmapClass']['0'] muss ein ['processCmdmapClass'][] gemacht werden, sonst überschreibt man Hooks von anderen Extensions (tt_news).

Noch als Ergänzung für die interessierte Allgemeinheit :-)
Hier noch die Hookfunktion fürs Kopieren von Veranstaltungen(Termine werden automatisch mitkopiert), die ich mal dazugescriptet habe. Die muss dazukopiert werden.

<?php
function processCmdmap_postProcess($command, $table, $id, $value, &$pObj)
{
  if ($command == 'copy' && $table == 'tx_weakentitytest_events' 
    and $pObj->copyMappingArray['tx_weakentitytest_events'])
  {
    // Ziel-ID herausfinden. Die steht im copyMappingArray
    $source = $id;
    $dest = $pObj->copyMappingArray['tx_weakentitytest_events'][$source];

    $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 
      'tx_weakentitytest_event_dates',
      'foreign_uid='.$source);

    while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))
    {
      // Datensätze kopieren. foreign_uid remappen
      // auf die neue Veranstaltungs-ID.
      $pObj->copyRecord('tx_weakentitytest_event_dates',$row['uid'],
        $row['pid'],0,array('foreign_uid' => $dest));
    }
  }
}
?>

Gruß,
Benjamin

Stoneage ·

Hallo Benjamin,

das BLOB-Feld kann man eventuell mit einer MM-Tabelle rausschmeissen. In die List-Ansicht kann man sich nur reinhacken (HOOK?), aber wie stellst Du Dir die Ansicht mit mehreren Terminen vor? Das wird ja voll die Fummelarbeit.

Gruß,

T.

BenjaminW ·

Hi Tetramatrix,

Tetramatrix schrieb

das BLOB-Feld kann man eventuell mit einer MM-Tabelle rausschmeissen.

Na dann hab ich ja immernoch die doppelte Referenzierung, und das auch noch in einer weiteren Table :-)

Tetramatrix schrieb

In die List-Ansicht kann man sich nur reinhacken (HOOK?), aber wie stellst Du Dir die Ansicht mit mehreren Terminen vor? Das wird ja voll die Fummelarbeit.

Ok, dann werd ich das wohl über ein eigenes Web->Modul lösen müssen.

Es ist halt z.Z. irgendwie nur ein riesiger Workaround. Typo3 kennt vom Core her keine identifizierende 1:n Beziehung (außer halt die "pid" Spalte bei z.B. tt_content).

Nachdenkliche Grüße,
Benjamin

Stoneage ·

Na dann hab ich ja immernoch die doppelte Referenzierung, und das auch noch in einer weiteren Table

Du könntest doch die foreign_uid in die mm_tabelle packen?

BenjaminW ·

Hi!

Tetramatrix schrieb

Du könntest doch die foreign_uid in die mm_tabelle packen?

Hm, geht das denn? Ich habe immer gedacht, dass die mm_table nicht bearbeitet werden kann, zumindest gibts ja dafür keine Konfiguration in der TCA, oder?

Gruß,
Benjamin

BenjaminW ·

Hi Tetram.,

Tetramatrix schrieb

das aber ne gute idee, oder?

Es geht halt nicht um den BLOB, sondern um diese zusätzliche Referenzierung der Veranstaltung zu Termine.
Letztlich gesehen ist das alles irgendwie murks :-) von vernünftigem DB-Design ist das weit entfernt.
Das DB Design sollte schon irgendwo entsprechenden Standards genügen (ist Vorgabe des Kunden). Jeder DB Fachmann kriegt Warzen wenn er das sieht *gg*

Ich bin halt noch ein wenig unschlüssig, ob das wirklich das Problem am Schopfe packt. Was wir bisher gemacht haben, ist, dass wir das foreign_table Feature ein wenig eingeschränkt haben. Was aber bleibt ist die Art und Weise, wie Typo3 die Referenzierungen macht. Eigentlich sollte die Zuweisung schon dadurch passiert sein, dass ein entsprechender Termin-Datensatz existiert und nicht noch durch eine weitere Zuweisung (ob per BLOB oder mm_table ist letztlich egal). Es ist ja keine M:N Beziehung, Typo3 macht aber eine.
Mein Formular sieht halt jetzt so aus, dass ich zwei Listen habe (links die zugewiesenen und rechts die vorhandenen). Das aber brauche ich eigentlich nicht, sondern nur eine Liste aller Termine, die ich im Veranstaltungs-Formular hinzufügen, bearbeiten und auch löschen (daran zerbreche ich mir grad den Kopf -> wizard) kann.

Grüße,
Benjamin

Stoneage ·

Hallo Benjamin,

stimmt, das zuweisen mit linker und rechter Spalte ist eigentlich unnötig. das könnte man auch rauschmeissen. meinst du findest eine lösung? Ich habe ja schon den Start gemacht...

Gruß,

T.

BenjaminW ·
Tetramatrix schrieb

das könnte man auch rauschmeissen. meinst du findest eine lösung? Ich habe ja schon den Start gemacht...

😉

Ich habe heute nochmal den ganzen Tag getüftelt. Mit Erfolg!
Zuerst habe ich aus dem "termine" Feld in Veranstaltungen ein User-defined Field gemacht. Die Liste baue ich nun selber auf. Außerdem habe ich die Wizards für Hinzufügen, Editieren und Löschen über TCEforms::renderWizards (ein Krampf war das!) um meinen Output gestülpt. Das klappt soweit und sieht auch gut aus. Leider funktioniert es noch nicht so wie ich es will. Delete musste ich über einen eigenen Wizard implementieren und Edit läuft auch noch nicht, weil wizard_edit.php anscheinend die ausgelesene ID nicht kriegt (er geht ja von zwei Listen aus, liest folglich die falsche aus), aber das schaffe ich glaube ich morgen schon (denke ich *gg*). Zur Not bastle ich auch noch den Edit-Wizard selbst...

Ich habe in der core api doku das Flag "noTableWrapping" gesehen (https://docs.typo3.org/m/typo3/reference-tca/main/en-us/ColumnsConfig/Type/User/Index.html).
Wenn ich das richtig verstehe, kann ich hiermit verhindern, dass diese Spalte in die Datenbank geschrieben wird , oder? *hoff*
Zur Not lass ich sonst einfach den BLOB leer, fürs Auslesen vom Frontend-Plugin aus brauch ich ihn ja nicht.

Sobald ich was zeigbares habe, werd ich das hier mal posten; es scheint ja immerhin eine Premiere zu sein ;-)

Nochmals vielen Dank für die Denkanstöße!

Gruß,
Benjamin

BenjaminW ·

Hallo Tetramatrix,

Tetramatrix schrieb

das feld wird einfach nicht in der tceform gerendert.

Richtig. Habe ich inzwischen auch mit Bedauern festgestellt. Ich habe einfach die Spalte als VARCHAR definiert. Das ist wenigstens performanter.

Tetramatrix schrieb

bist du weiter gekommen?

Ja! Es läuft soweit. Hat noch ein paar Kinderkrankheiten, für die ich noch die richtige Medizin suche. Beispielsweise möchte ich Start und Endzeiten bei der Eingabe überprüfen (per Javascript am besten), ob Startzeit kleiner als Endzeit ist.

Sobald ich die Extension für mein derzeitiges Projekt fertig habe, werde ich mich mal an nem WE ransetzen und die wesentlichen Dinge rausisolieren und hier posten bzw. vielleicht mal als Beispiel-Extension auf Typo3.net.

Gruß,
Benjamin