Intermediate relations with additional data in Typo3
Initial situation
We have 2 connected extbase models A — M:N — B via a intermediate table. In the TCA they are connected via selectMultipleSideBySide. Our goal is to extend this relation so it can hold more information about the connection.
Current backend
Simple direct relation:
Desired outcome
Adding more information to this relation:
Existing code
Model A
namespace Vendor\Example\Domain\Model;
class A extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
/**
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Example\Domain\Model\B>
*/
protected $relationToB = null;
// getter/setter yada yada yada
}
TCA of Model A
…
config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_example_domain_model_B',
'MM' => 'tx_example_A_B_mm',
…
],
…
SQL
CREATE TABLE tx_example_domain_model_A (
-- …
relation_to_B int(11) unsigned DEFAULT '0' NOT NULL,
-- …
);
CREATE TABLE tx_example_domain_model_B (
-- …
);
CREATE TABLE tx_example_A_B_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)
);
Necessary conversion to allow data in the Intermediate table
Overview
- Change TCA of Model A to field type inline
- Add TCA for intermediate table (was not necessary until now)
- Extend Intermediate db table with necessary fields (including additional data fields)
- Add a model for the relation to use data in extbase
- Switch relation field B in Model A to this new relation model
- Add repository for relation model
- Add class mapping for relation model
- Clear every cache you can
Not that difficult isn’t it? 😀 Let’s do this!
1. Change TCA of Model A to field type inline
<?php
return [
'columns' => [
'relation_to_b' => [
'config' => [
'type' => 'inline',
'foreign_table' => 'tx_example_A_B_mm',
'foreign_field' => 'uid_foreign',
'foreign_label' => 'uid_local',
'foreign_selector' => 'uid_local',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_match_fields' => [
'fieldname' => 'relation_to_b'
]
]
]
]
]
2. Add TCA for intermediate table (was not necessary until now)
File example/Configuration/TCA/tx_example_A_B_mm.php
<?php
return [
'ctrl' => [
'title' => 'Relation to B',
'label' => 'uid_local',
'label_alt' => '', // actually important to add for display in backend!
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'type' => 'uid_local:type',
'hideTable' => true,
'delete' => 'deleted',
'versioningWS' => true,
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'rootLevel' => -1,
'enablecolumns' => [
'disabled' => 'hidden',
],
'typeicon_classes' => [
'default' => 'mimetypes-other-other',
],
'security' => [
'ignoreWebMountRestriction' => true,
'ignoreRootLevelRestriction' => true,
],
'searchFields' => 'field_1, field_2',
],
'columns' => [
'sys_language_uid' => [
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [
'type' => 'language',
],
],
'l10n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0',
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
'config' => [
'type' => 'group',
'allowed' => 'tx_example_A_B_mm',
'size' => 1,
'maxitems' => 1,
'default' => 0,
],
],
'l10n_diffsource' => [
'config' => [
'type' => 'passthrough',
'default' => '',
],
],
'hidden' => [
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden',
'config' => [
'type' => 'check',
'default' => 0,
],
],
'uid_local' => [
'label' => 'B',
'config' => [
'type' => 'group',
'size' => 1,
'maxitems' => 1,
'allowed' => 'tx_example_domain_model_B',
'hideSuggest' => true,
],
],
'uid_foreign' => [
'label' => 'Used by content elements',
'config' => [
'type' => 'input',
'size' => 10,
'eval' => 'int',
],
],
'tablenames' => [
'label' => 'Tablename',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
],
],
'fieldname' => [
'label' => 'Foreign Field',
'config' => [
'type' => 'input',
'size' => 30,
],
],
'sorting_foreign' => [
'label' => 'Sorting foreign',
'config' => [
'type' => 'input',
'size' => 4,
'max' => 4,
'eval' => 'int',
'default' => 0,
],
],
'table_local' => [
'label' => 'Local table',
'config' => [
'type' => 'input',
'size' => 20,
'default' => 'tx_example_domain_model_B',
],
],
'example_field_1' => [
'exclude' => true,
'label' => 'Example select field',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [
['None','0'],
['Item 1','1'],
['Item 2','2'],
['Item 3','3'],
],
'default' => 0,
],
],
'example_field_2' => [
'exclude' => true,
'label' => 'Example toggle field',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
],
],
],
'types' => [
'0' => [
'showitem' => '
--palette--;;basicoverlayPalette,
--palette--;;filePalette',
],
],
'palettes' => [
'basicoverlayPalette' => [
'label' => 'Model B (Additional intermediate fields)',
'showitem' => 'example_field_1, --linebreak--, example_field_2',
],
// File palette, hidden but needs to be included all the time
'filePalette' => [
'showitem' => 'uid_local, hidden, sys_language_uid, l10n_parent',
'isHiddenPalette' => true,
],
],
];
3. Extend Intermediate db table with necessary fields (including additional data fields)
CREATE TABLE tx_example_A_B_mm
(
uid int(11) NOT NULL auto_increment, -- new!
pid int(11) DEFAULT '0' NOT NULL, -- new!
uid_local int(11) unsigned DEFAULT '0' NOT NULL,
uid_foreign int(11) unsigned DEFAULT '0' NOT NULL,
tablenames varchar(64) DEFAULT '' NOT NULL, -- new!
fieldname varchar(64) DEFAULT '' NOT NULL, -- new!
sorting int(11) unsigned DEFAULT '0' NOT NULL,
sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL,
table_local varchar(64) DEFAULT '' NOT NULL, -- new!
# Local usage overlay fields
example_field_1 int(11) unsigned DEFAULT '0' NOT NULL, -- new!
example_field_2 tinyint(4) unsigned DEFAULT '0' NOT NULL, -- new!
PRIMARY KEY (uid),
KEY tablenames_fieldname (example_field_1(32),example_field_2(12)), -- new!
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);
I had to flip the existing values from tx_example_A_B_mm.uid_local
and ….uid_foreign
for some reason.
You can do this by the following SQL Statement:
UPDATE tx_example_A_B_mm SET uid_local=(@temp:=uid_local), uid_local = uid_foreign, uid_foreign = @temp;
4. Add a model for the relation to use data in extbase
<?php
namespace Vendor\Example\Domain\Model;
class RelationToB extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
/**
* @var \Vendor\Example\Domain\Model\B
*/
protected $relationToB = null;
/**
* exampleField1
*
* @var int
*/
protected $exampleField1 = 0;
/**
* exampleField2
*
* @var boolean
*/
protected $exampleField2 = false;
// add getter and setting functions…
}
5. Switch relation field B in Model A to this new relation model
// change every ocurrence of:
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Example\Domain\Model\B>
// to:
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Example\Domain\Model\RelationToB>
6. Add repository for relation model
In example/Classes/Domain/Repository/RelationToBRepository.php
<?php
namespace Vendor\Example\Domain\Repository;
class RelationToBRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
}
7. Add class mapping for relation model
Tell extbase how resolve the new relation in example/Configuration/Extbase/Persistence/Classes.php
<?php
declare(strict_types=1);
return [
\Vendor\Example\Domain\Model\RelationToB::class => [
'tableName' => 'tx_example_A_B_mm',
'properties' => [
'relation_to_b' => [
'fieldName' => 'uid_local',
],
],
],
];
8. Clear every cache you can
composer dumpautoload
or in non-composer projectRebuild PHP Autoload Information
Flush TYPO3 and PHP Cache
Flush all caches
in top navigation