Zwischen den beiden Models Seminar und Category soll eine m:m Relation erstellt werden. Die Relation soll im TYPO3 Backend sowie im Extbase-Kontext von beiden Seiten aus zugänglich sein (bidirektional).

Auf Datenbankebene ist in beiden Tabellen eine int-Spalte erforderlich, idealerweise benannt nach dem referenzierten Modell (also categories und seminars). Diese Spalten sind eigentlich nur für Extbase erforderlich, die eigentliche Relation wird in einer Relationstabelle tx_myext_seminar_category_mm gespeichert. Wichtig für eine Sortiermöglichkeit auch von der Gegenseite aus ist hierbei die Spalte sorting_foreign.

Im TCA ist für beide Tabellen zunächst eine gewöhnliche Konfiguration des Relationsfeldes (categories bzw. seminars) als m:m-Relation erforderlich (type=select, foreign_table, foreign_table_where und MM). Zusätzlich muss auf der Gegenseite (category-Tabelle) über MM_opposite_field die Spalte der referenzierten Tabelle definiert werden.

Im jeweiligen Extbase Model werden die Properties categories und seminars als Objectstorage vom Type des referenzierten Models definiert (abhängig vom Anwendungsfall als @lazy). Es fehlen dann nur noch die Getter und Setter (add, remove, get und set).

Datenbank

#
# Table structure for table 'tx_myext_domain_model_seminar'
#
CREATE TABLE tx_myext_domain_model_seminar (
	uid int(11) NOT NULL auto_increment,
	pid int(11) DEFAULT '0' NOT NULL,
	title varchar(255) DEFAULT '' NOT NULL,
	categories int(11) unsigned DEFAULT '0' NOT NULL,
	#...
	PRIMARY KEY (uid),
	KEY parent (pid),
	KEY language (l10n_parent,sys_language_uid)
);

#
# Table structure for table 'tx_myext_domain_model_category'
#
CREATE TABLE tx_myext_domain_model_category (
	uid int(11) NOT NULL auto_increment,
	pid int(11) DEFAULT '0' NOT NULL,
	title varchar(255) DEFAULT '' NOT NULL,
	seminars int(11) unsigned DEFAULT '0' NOT NULL,
	#...
	PRIMARY KEY (uid),
	KEY parent (pid),
	KEY language (l10n_parent,sys_language_uid)
);

#
# Table structure for table 'tx_myext_seminar_category_mm'
#
CREATE TABLE tx_myext_seminar_category_mm (
	uid_local int(11) unsigned DEFAULT '0' NOT NULL,
	uid_foreign int(11) unsigned DEFAULT '0' NOT NULL,
	sorting int(11) unsigned DEFAULT '0' NOT NULL,
	sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL,
	KEY uid_local (uid_local),
	KEY uid_foreign (uid_foreign)
);

TCA Konfiguration

$TCA['tx_myext_domain_model_seminar'] = array(
    /* ... */
    'columns' => array(
        'categories' => array(
            'exclude' => 1,
            'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xml:tx_myext_domain_model_seminar.categories',
            'config' => array(
                'type' => 'select',
                'multiple' => 1,
                'foreign_table' => 'tx_myext_domain_model_category',
                'MM' => 'tx_myext_seminar_category_mm',                
                'foreign_table_where' => ' AND tx_myext_domain_model_category.pid=###CURRENT_PID### ORDER BY tx_myext_domain_model_category.title ',
                'minitems' => 0,
                'maxitems' => 99,
                /* ... */              
            ),
        ),
    ),
);

$TCA['tx_myext_domain_model_category'] = array(
    /* ... */
    'columns' => array(
        'seminars' => array(
            'exclude' => 1,
            'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xml:tx_myext_domain_model_category.seminars',
            'config' => array(
                'type' => 'select',
                'multiple' => 1,
                'foreign_table' => 'tx_myext_domain_model_seminar',
                'MM' => 'tx_myext_seminar_category_mm',  
                'MM_opposite_field' => 'seminars',
                'foreign_table_where' => ' AND tx_myext_domain_model_seminar.pid=###CURRENT_PID### ORDER BY tx_myext_domain_model_seminar.title ',
                'minitems' => 0,
                'maxitems' => 99,
                /* ... */              
            ),
        ),        
    ),
);

Extbase Model

class Seminar extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {

	/**
	 * Categories
	 *
	 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Myext\Domain\Model\Category>
	 * @lazy
	 */
	protected $categories;

	/**
	 * Adds a Category
	 *
	 * @param \Vendor\Myext\Domain\Model\Category $category
	 * @return void
	 */
	public function addCategory(\CVendor\Myext\Domain\Model\Category $category) {
		$this->categories->attach($category);
	}

	/**
	 * Removes a Category
	 *
	 * @param \CVendor\Myext\Domain\Model\Category $categoryToRemove The Category to be removed
	 * @return void
	 */
	public function removeCategory(\Vendor\Myext\Domain\Model\Category $categoryToRemove) {
		$this->categories->detach($categoryToRemove);
	}    
    
	/**
	 * Returns the Categories
	 *
	 * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Myext\Domain\Model\Category> $categories
	 */
	public function getCategories() {
		return $this->categories;
	}

	/**
	 * Sets the Categories
	 *
	 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Myext\Domain\Model\Category> $categories
	 * @return void
	 */
	public function setCategories(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $categories) {
		$this->categories = $categories;
	} 

}

class Category extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {

	/**
	 * Seminars
	 *
	 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Myext\Domain\Model\Seminar>
	 * @lazy
	 */
	protected $seminars;

	/**
	 * Adds a Seminar
	 *
	 * @param \Vendor\Myext\Domain\Model\Seminar $seminar
	 * @return void
	 */
	public function addSeminar(\Vendor\Myext\Domain\Model\Seminar$seminar) {
		$this->seminars->attach($seminar);
	}

	/**
	 * Removes a Seminar
	 *
	 * @param \Vendor\Myext\Domain\Model\Seminar $seminarToRemove The Seminar to be removed
	 * @return void
	 */
	public function removeSeminar(\Vendor\Myext\Domain\Model\Seminar $seminarToRemove) {
		$this->seminars->detach($seminarToRemove);
	}

	/**
	 * Returns the Seminars
	 *
	 * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Myext\Domain\Model\Seminar> $seminars
	 */
	public function getSeminars() {
		return $this->seminars;
	}

	/**
	 * Sets the Seminars
	 *
	 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Myext\Domain\Model\Seminar> $seminars
	 * @return void
	 */
	public function setSeminars(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $seminars) {
		$this->seminars = $seminars;
	}

}




Kommentare

David Bascom schrieb am 07.02.2016:
Noch ein wichtiger Hinweis, an dem wir heute verzweifelt sind:
Neben dem DB-Feld "sorting_foreign" in der MM-Tabelle scheint ab Version 6.2 zwingend notwendig zu sein, in der TCA-Definition multiple=>1 zu setzen, sonst werden die Datensätze nicht gespeichert.

Das wird auch kurz in der Dokumentation erwähnt – leider sind die meisten Tutorials im Netz aber dazu veraltet und berücksichtigen diese Einstellung nicht:
https://docs.typo3.org/typo3cms/TCAReference/Reference/Columns/Select/Index.html#mm-opposite-field

Wir haben diese Erkenntnisse hier mal kurz zusammengefasst:
http://labor.99grad.de/?p=1036

TYPO3 Tiger schrieb am 11.02.2016:
Hi David, danke für deinen Hinweis. Habe das Beispiel entsprechend ergänzt. Hier noch der korrekte Link zur Reference:

https://docs.typo3.org/typo3cms/TCAReference/Reference/Columns/Select/Index.html#multiple

steffi schrieb am 08.05.2017:
Gibt es evlt. zu diesem Beispiel ein weiteres Tutorial, dass die Ausgabe dieser Relation im Frontend über den Controller steuert?

Christan Michael schrieb am 29.12.2017:
Funktioniert prima!