TYPO3-Tutorial: Extension mit N zu M Datenbankrelationen erstellen

Der Kickstarter für TYPO3-Extensions gibt einem Entwickler viele Möglichkeiten Datenbanken anzulegen, sogar MM-Tabellen. Aber leider lassen sich viele Funktionen, die modernes Datenbankdesign bietet, mit dem Kickstarter nicht umsetzen. Dieser Artikel gibt einen Einblick, wie in TYPO3 die wichtigsten Relationen zwischen Tabellen

  • M zu M
  • N zu M
  • N zu M mit Attributen

angelegt werden können.

NEU: Zu diesem Eintrag gibt es jetzt auch eine Demo-Extension im TYPO3-Extension-Repository: uk Datenbank Tutorial (t3uk_db_tutorial)

Auf 1:N Relationen wird hier nicht eingegangen, da diese vom Kickstarter voll unterstützt und einfach zusammen geklickt werden können.

Worum geht es hier genau?

Fangen wir mit einem einfachen Beispiel an. Für eine Freelancer-Datenbank die mit TYPO3 umgesetzt werden soll brauchen wir zwei Datensatztypen:

  • Mitarbeiter oder Freelancer sowie
  • Firmen bei für die diese Mitarbeiter gerade arbeiten.

Logischerweise sollen diese Daten miteinander verknüpft werden können. Mitarbeiter arbeiten für verschiedenen Firmen und für Firmen arbeiten verschiedenen Mitarbeiter. Nun soll die Eingabe der Verbindungen Mitarbeiter-Firma so flexibel sein, dass wir sie von beiden Seiten aus bearbeiten können.

Wird also zum Beispiel einem Mitarbeiter-Datensatz A eine Firma B zugeordnet, so sieht man auch im Firmen-Datensatz B, dass der Mitarbeiter bei der Firma arbeitet. Man kann so die Verbindung zwischen dem Mitarbeiter und der Firma sowohl beim Bearbeiten des Firmen-, als auch des Mitarbeiter-Datensatzes wieder lösen. Ferner kann natürlich der Firma B ein Mitarbeiter C zugeordnet werden und im Mitarbeiter-Datensatz C erscheint dann automatisch die Firma B.

In einem weiteren Schritt werden wir auch noch erklären, wie man der Verbindung noch weitere Eigenschaften wie Werksvertrag, studentische Hilfskraft oder freier Mitarbeiter hinzufügt.

Doch bereits das erste Szenario ist mit dem TYPO3-Kickstarter nicht mehr möglich: Wird mit dem eine Tabelle Firma angelegt mit dem Feld Mitarbeiter (Relation zu Mitarbeiter) und ein Mitarbeiter mit dem Feld Firma, so macht der Kickstarter daraus zwei M:M Tabellen. Es ist aber klar, dass sowohl Mitarbeiter als auch Firma über ein und dieselbe Relationstabelle verknüpft werden müssen, um die gewünschte, oben beschriebene Funktionalität zu erhalten. Dies wird ermöglicht mit 'foreign_table' =>'' und 'foreign_field' => '' welche in der tca.php der Extension definiert werden können.

Ein Beispiel der gewünschten Datenbankrelation:

Ein Projekt-Mitarbeiter kann für mehrere verschiedene Firmen arbeiten und eine Firma hat mehrere verschieden Mitarbeiter. Zunächst werden beide Tabellen im Kickstarter angelegt:

In der Tabelle Mitarbeiter wird nun eine Relation zur Tabelle Firma definiert:

In der Tabelle Firma wird eine Relation zur Tabelle Mitarbeiter definiert:

Beide Relationen müssen als True M-M relations markiert werden, da sonst keine extra Relationstabelle angelegt wird. Das beidseitige Anlegen von Mitarbeiter und Firma ermöglicht später das beidseitige Editieren (siehe oben).

Schaut man sich nun die ext_tables.sql der Exension an, erkennt man, dass zwei MM Relationstabellen angelegt werden. Eine für die Richtung Mitarbeiter->Firma und eine für Firma->Mitarbeiter: ext_tables.sql

CREATE TABLE tx_ukdbtutorial_mitarbeiter_firma_mm (
uid_local int(11) DEFAULT '0' NOT NULL,
uid_foreign int(11) DEFAULT '0' NOT NULL,
ablenames varchar(30) DEFAULT '' NOT NULL, 
sorting int(11) DEFAULT '0' NOT NULL,
KEY uid_local (uid_local), KEY uid_foreign (uid_foreign)
);

CREATE TABLE tx_ukdbtutorial_firma_mitarbeiter_mm (
uid_local int(11) DEFAULT '0' NOT NULL,
uid_foreign int(11) DEFAULT '0' NOT NULL,
tablenames varchar(30) DEFAULT '' NOT NULL,
sorting int(11) DEFAULT '0' NOT NULL,
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);

Es werden aber nicht beide benötigt, daher kann getrost eine Tabelle aus der Datei gelöscht werden, z.B. tx_ukdbtutorial_firma_mitarbeiter_mm. Außerdem muss bei der Relationstabelle ein Feld hinzugefügt werden: sorting_foreign int(11) DEFAULT '0' NOT NULL.

Dies ermöglicht das unterschiedliche Sortieren der Datensätze auf beiden Seiten und muss erstellt werden, da TYPO3 danach verlangt. Das Resultat sieht dann also so aus:

CREATE TABLE tx_ukdbtutorial_mitarbeiter_firma_mm (
uid_local int(11) DEFAULT ‚0‘ NOT NULL,
uid_foreign int(11) DEFAULT ‚0‘ NOT NULL,
tablenames varchar(30) DEFAULT “ NOT NULL,
sorting int(11) DEFAULT ‚0‘ NOT NULL,
sorting_foreign int(11) DEFAULT ‚0‘ NOT NULL,
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);

Jetzt muss noch dafür gesorgt werden, dass sowohl Firma als auch Mitarbeiter auf dieselbe Relationstabelle zugreifen:

$TCA[„tx_ukdbtutorial_firma“] = array (
„mitarbeiter“ => Array (
„exclude“ => 1,
„label“ => „LLL:EXT:uk_db_tutorial/locallang_db.xml:tx_ukdbtutorial_firma.mitarbeiter“,
„config“ => Array (
„type“ => „group“,
„internal_type“ => „db“,
„allowed“ => „tx_ukdbtutorial_mitarbeiter“,
„size“ => 10,
„minitems“ => 0,
„maxitems“ => 10,
„MM“ => „tx_ukdbtutorial_firma_mitarbeiter_mm“,
)

wird geändert in:

$TCA[„tx_ukdbtutorial_firma“] = array (
„mitarbeiter“ => Array (
„exclude“ => 1,
„label“ => „LLL:EXT:uk_db_tutorial/locallang_db.xml:tx_ukdbtutorial_firma.mitarbeiter“,
„config“ => Array (
„type“ => „group“,
„internal_type“ => „db“,
„allowed“ => „tx_ukdbtutorial_mitarbeiter“,
„size“ => 10,
„minitems“ => 0,
„maxitems“ => 10,
„MM“ => „tx_ukdbtutorial_mitarbeiter_firma_mm“,
„MM_opposite_field“ => „mitarbeiter“,

"MM" => "tx_ukdbtutorial_mitarbeiter_firma_mm" ist die zu benutzende Relationstabelle, sie muss sowohl bei Mitarbeiter als auch bei Firma dieselbe sein.
"MM_opposite_field" => "mitarbeiter" sorgt dafür das die uids in der Relationstabelle nicht vertauscht werden . Diese Relation ist jetzt eigentlich eine N:M Relation da, z.B. mehrere Mitarbeiter einer Firma zugeordnet werden können, die Anzahl der Zuordnungen auf beiden Seiten aber nicht gleich sein muss. Möchte man dies haben sollten die Max number of relations passend gesetzt werden.

N:M Relation mit Attributen

Ein Mitarbeiter kann in mehreren Firmen arbeiten, in diesen kann er bestimmte Positionen, z.B. Werksvertrag, studentische Hilfskraft, fester freier Mitarbeiter besitzen. Dies sind die Attribute.

Ein Beispiel: N zu M Relation mit Attributen

Es kann aber vorkommen, das ein Mitarbeiter in der Firma A einen Werksvertrag hat und in der Firma B studentische Hilfskraft ist. Es ist also nicht möglich die Position des Mitarbeiters in einem Feld in der Tabelle Mitarbeiter zu speichern, da die Position von der jeweiligen Firma abhängt. Das Attribut Position muss also in der Relationstabelle gespeichert werden. Dies lässt sich am besten mit Inline Rational Relation Editing (IRRE) realisieren:

Zunächst legt man zwei neue Tabellen an: Mitarbeiter2 und Firma2. Dies kann exakt, wie oben beschrieben, mit Hilfe des Kickstarters erfolgen.

Jetzt müssen wieder die tca.php und die ext_tables.sql editiert werden:
ext_tables.sql:

CREATE TABLE tx_ukdbtutorial_mitarbeiter2_firma_mm (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT ‚0‘ NOT NULL,
tstamp int(11) DEFAULT ‚0‘ NOT NULL,
tablenames varchar(30) DEFAULT “ NOT NULL,
sorting int(11) DEFAULT ‚0‘ NOT NULL,
position tinytext NOT NULL,
mitarbeiterid int(11) DEFAULT ‚0‘ NOT NULL,
firmaid int(11) DEFAULT ‚0‘ NOT NULL,
crdate int(11) DEFAULT ‚0‘ NOT NULL,
cruser_id int(11) DEFAULT ‚0‘ NOT NULL,
deleted tinyint(4) DEFAULT ‚0‘ NOT NULL,
PRIMARY KEY (uid),
KEY parent (pid)
);
);

Die zweite Relationstabelle tx_ukdbtutorial_firma2_mitarbeiter_mm kann wieder gelöscht werden. Wir benötigen jetzt das neue Feld position und 2 Felder zu Steuerung mitarbeiterid und firmaid. uid_local und uid_foreign werden als Felder nicht mehr benötigt, dafür aber eine uid und eine pid. Die uid ist wie immer der Primary Key und wird daher auf auto_increment gesetzt.
cruser, tstamp, deleted und crdate werden von TYPO3 benötigt. Man sieht jetzt schon das die Relationstabelle nicht mehr klassisch aufgebaut ist , sondern wie eine ganz normale "Datensatz"-Tabelle., dazu später mehr.

In der tca.php muss nun etwas mehr geändert werden:

$TCA[„tx_ukdbtutorial_mitarbeiter2“] = array (
„interface“ => array (
„showRecordFieldList“ => „hidden,name,firma“
),
„firma“ => Array (
„exclude“ => 1,
„label“ => „LLL:EXT:uk_db_tutorial/locallang_db.xml:tx_ukdbtutorial_mitarbeiter2.firma“,
„config“ => Array (
‚type‘ => ‚inline‘,
‚foreign_table‘ => ‚tx_ukdbtutorial_mitarbeiter2_firma_mm‘,
‚foreign_field‘ => ‚mitarbeiterid‘,
‚foreign_label‘ => ‚firmaid‘,
),
),
$TCA[„tx_ukdbtutorial_firma2“] = array (
„interface“ => array (
„showRecordFieldList“ => „hidden,name,mitarbeiter“
),
„mitarbeiter“ => Array (
„exclude“ => 1,
„label“ => „LLL:EXT:uk_db_tutorial/locallang_db.xml:tx_ukdbtutorial_firma2.mitarbeiter“,
„config“ => Array (
‚type‘ => ‚inline‘,
‚foreign_table‘ => ‚tx_ukdbtutorial_mitarbeiter2_firma_mm‘,
‚foreign_field‘ => ‚firmaid‘,
‚foreign_label‘ => ‚mitarbeiterid‘,
),
),

type‘ => ‚inline‘: Jetzt wird nicht länger die Groupbox von TYPO3 benutzt, sondern IRRE. Das heißt, dass Kinderelemente oder Relationen direkt in dem Mitarbeiter bzw. der Firma angelegt werden können.
foreign_table‘ =>: Definiert welche Relationstabelle benutzt wird, muss auch hier wieder gleich sein.
Achtung es darf kein „MM“ => benutzt werden, da dann keine Attribute mehr unterstützt werden.

Außerdem muss die Relationstabelle in der tca.php definiert werden:

$TCA[‚tx_ukdbtutorial_mitarbeiter2_firma_mm‘] = Array(
„ctrl“ => $TCA[„tx_ukdbtutorial_mitarbeiter2_firma_mm“][„ctrl“],
„interface“ => Array (
„showRecordFieldList“ => „mitarbeiterid,firmatid,position“
),
„feInterface“ => $TCA[„tx_ukdbtutorial_mitarbeiter2_firma_mm“][„feInterface“],
‚columns‘ => Array(
‚mitarbeiterid‘ => Array(
‚label‘ => ‚LLL:EXT:uk_db_tutorial/locallang_db.xml:tx_ukdbtutorial_mitarbeiter2‘,
‚config‘ => Array(
‚type‘ => ’select‘,
‚foreign_table‘ => ‚tx_ukdbtutorial_mitarbeiter2′,
’size‘ => 1,
‚minitems‘ => 0,
‚maxitems‘ => 1,
),
),
‚firmaid‘ => Array(
‚label‘ => ‚LLL:EXT:uk_db_tutorial/locallang_db.xml:tx_ukdbtutorial_firma2‘,
‚config‘ => Array(
„type“ => „group“,
„internal_type“ => „db“,
„allowed“ => „tx_ukdbtutorial_firma2“,
„size“ => 1,
„minitems“ => 1,
„maxitems“ => 1,)
),
‚position‘ => Array(
‚label‘ => ‚LLL:EXT:uk_db_tutorial/locallang_db.xml:tx_ukdbtutorial_mitarbeiter2_firma_mm.position‘,
‚config‘ => Array(
„type“ => „input“,
„size“ => „30“,
),
),
),
„types“ => Array (
„0“ => Array(„showitem“ => „hidden;;1,firmaid,mitarbeiterid,position“)
),
„palettes“ => Array (
„1“ => Array(„showitem“ => „“)
)
);

Die Konfiguration für das Backend wurde hier so gestaltet, dass bestehende Mitarbeiter über eine Selectorbox und bestehende Firmen über eine eine Groupbox ausgewählt werden können. Die Eingabe der Position kann über ein normales Input Feld erfolgen. In der ext_tables.php wird nun ebenfalls die Relationstabelle definiert:

Die Konfiguration für das Backend wurde hier so gestaltet, dass bestehende Mitarbeiter über eine Selectorbox und bestehende Firmen über eine eine Groupbox ausgewählt werden können.
Die Eingabe der Position kann über ein normales Input Feld erfolgen.

In der ext_tables.php wird nun ebenfalls die Relationstabelle definiert:

$TCA[„tx_ukdbtutorial_mitarbeiter2_firma_mm“] = Array (
„ctrl“ => Array (
‚title‘ => ‚LLL:EXT:uk_db_tutorial/locallang_db.xml:tx_ukdbtutorial_mitarbeiter2_firma_mm‘,
‚label‘ => ‚uid‘,
‚tstamp‘ => ‚tstamp‘,
‚crdate‘ => ‚crdate‘,
„dynamicConfigFile“ => t3lib_extMgm::extPath($_EXTKEY).“tca.php“,
„iconfile“ => t3lib_extMgm::extRelPath($_EXTKEY).“icon_tx_ukdbtutorial_firma2.gif“,
),
„feInterface“ => Array (
„fe_admin_fieldList“ => „mitarbeiterid, firmaid, position“,
)
);

Jetzt müssen nur noch die Labels für die Relationstabelle in der locallang_db.xml gesetzt werden:

<label index="tx_ukdbtutorial_mitarbeiter2_firma_mm">Mitarbeiter-Firma</label>
<label index="tx_ukdbtutorial_mitarbeiter2_firma_mm.position">Position</label>

Nun kann man das Resultat im Backend bewundern, das Ganze sieht dann wie folgt aus:

TYPO3 Datenbankrelation - Backend N zu M

Wie man sieht , können, jetzt auch die Relation an sich bearbeitet werden. Sie erscheinen in dem Sysordner als regulärer Datensatz. Bearbeitung der Firma:

TYPO3 Datenbankrelation - Backend N zu M - Firma

Bearbeitung des Mitarbeiters:

TYPO3 Datenbankrelation - Backend N zu M - Mitarbeiter

Zum Verständnis: Neu anlegen meint in diesem Fall das Neuanlegen einer Relation, es können keine neuen Firmen innerhalb eines Mitarbeiters angelegt werden, sondern nur bestehende ausgewählt werden.

Weiterführende Links

symbol: t3uk datenbank tutorial, TYPO3 uk Datenbank Tutorial (t3uk_db_tutorial)

Näheres zu IRRE

Ein sehr ausführliches Tutorial zu IRRE:

Podcast zu IRRE

Es gibt noch sehr viele Möglichkeiten das Backend für die Datensätze zu gestalten: TYPO3 Core Library


Kommentare

Perfekt :) das ist genau das was ich brauche! danke!

Die Demo-Extension enthält in der tca.php ein paar Fehler: Das Feld "allowed" verweist an mehreren Stellen auf falsche Tabellen-Namen mit fehlendem t3-Prefix, z.B. (tx_ukdbtutorial_mitarbeiter statt tx_t3ukdbtutorial_mitarbeiter).

Außerdem läuft das Beispiel bei uns nicht unter 6.2 – vermutlich aus folgenden beiden Gründen:

1. In der MM-Tabelle muss es ein Feld sorting_foreign int(11) DEFAULT '0' NOT NULL geben

2. In beiden TCA-Definitionen muss 'multiple' =&gt; 1 gesetzt werden.

Wir haben beides mal hier an einem Beispiel zusammengefasst:

http://labor.99grad.de/?p=1036

Hi,

also der Artikel ist von 2008 und daher nicht mehr ganz aktuell. ,-)

Ich werde versuchen nächste Wochen eine aktualisierte Version für Extbase bereit zu stellen.

Generell aber: Ja das geht auch alles mit Extbase bzw. Typo3 6.2

Hat es jemand hinbekommen, dass ganze korrekt mit Extbase-Models abzubilden? Die Datenbearbeitung über das TYPO3-BE funktioniert bei mir zwar tadellos, jedoch kann ich die Daten nicht per Extbase abfragen, die per MM-Relation verknüpften Daten werden nicht zurück gegeben.

Würde mich über Hinweise freuen, wie man das richtig umsetzt bzw. ob das überhaupt funktioniert.

Moin,

bei mir funktioniert die Relation mit Attribut im Backend wunderbar, auch in der Datenbank sieht alles sauber aus. Wenn ich nun allerdings ein Plugin anlege, um die Daten auszugeben, erzeugt mir die Standard Repository-Funktion findAll() einen SQL-Error, da versucht wird, die Zuweisungsfelder in der Firmen-Tabelle zu finden, statt in der Relation-Tabelle. Hab ansonsten den Code hier quasi 1:1 übernommen. Hat da jemand eine Idee, worans liegen kann ?

Danke, hast mein Wochenende gerettet.

“Man kann IRRE aber auch so konfigurieren, das neue Kindelemente direkt angelegt werden können.”

auch ich würde mich hier über hinweise freuen!

Das "MM_opposite_field" =&gt; "mitarbeiter" darf im TCA nur für die 2. Tabelle eingetragen sein, also für die wo es keine eigene mm-Tabelle gibt! Sonst funktioniert es nicht richtig bzw. die uids werden nicht richtig getauscht.

Damit habe ich mich jetzt ewig herumgeschlagen bis ich das herausgefunden habe ;)

"Man kann IRRE aber auch so konfigurieren, das neue Kindelemente direkt angelegt werden können."

Das wäre genau das, was ich brauche. Habt ihr Hinweisem wie deas in dem TCA konfiguriert werden kann?

Wenn Kinderlemente nicht direkt in einer Unterform angelegt werden können, ist IRRE ja nur halb so viel wert. Denn wenn ich z.B. einer Firma einen Mitarbeiter hinzufügen will, der noch nicht existiert, muss ich umstädnlich zuerst zur Mitarbeitertabelle, dort den Mitarbeiter anlegen, dann wieder zurück zur Firma und ihn dann mittels der Relation hinzufügen. Das wäre nicht wirklich cool.

super das ist genau das was ich brauche! danke!

Danke!!! Ein toller Beitrag. Ich verstricke mich grade in die vielfältigen Möglichkeiten bei der Extension Programmierung und dieser Artikel war ein grosser Beitrag beim entwickeln strukturierter komplexer Extensions.

PS: Die Bilder beim Anlegen von M-M Tabellen für Mitarbeiter und Firma sind gleich, und stellen nur die Erstellung des Feldes für die Tabelle "Mitarbeiter" dar. Die Erstellung des Feldes der Tabelle "Firma" wird nicht dargestellt. Hat mich 5min verwirrt. :)

IRRE heisst: Inline Relational Record Editing

und nicht "Inline Rational Relation Editing"

Mercie, der Beitrag hat mir sehr geholfen die Datenbank einer unserer Webseiten umzugestalten.

Hallo

Also danke für dieses Tut - aber eine Frage hätte ich wie geht das ganze mit der Ausgabe für das Frontend - also Backend ist mir alles soweit klar!

Weis aber nicht wie ich diese Elemente dann auf der Webseite ausgeben kann! Gibt es dazu vielleicht auch ein TUT wäre super Danke für diesen super BLOG!!!


Kommentar schreiben

* Diese Felder sind erforderlich