-->

TYPO3 extbase & IRRE: add existing records with &#

2020-02-11 09:12发布

问题:

I "kickstarted" an extension with the extbase extension builder that contains some 1:1 and 1:n relations. It automatically set the field types to 'inline' and displayed a nice IRRE UI in the backend.

But by default, there is no way to select an existing record, just create new ones.

I found various explanations on how to achieve this with 'foreign_selector', but all of them very sketchy. The feature itself should be working, see https://forge.typo3.org/issues/43239

Can someone walk me through this or point to a working example in the TER? I could create a step-by-step tutorial from the example, once I get it to work.

PS The field's TCA config as generated by extension_builder:

'myfield' => array(
    'exclude' => 1,
    'label' => 'LLL:EXT:myextension/Resources/Private/Language/locallang_db.xlf:tx_myextension_domain_model_myitem.myfield',
    'config' => array(
        'type' => 'inline',
        'foreign_table' => 'tx_myextension_domain_model_myfield',
        'foreign_field' => 'myitem',
        'maxitems'      => 9999,
        'appearance' => array(
            'collapseAll' => 0,
            'levelLinksPosition' => 'top',
            'showSynchronizationLink' => 1,
            'showPossibleLocalizationRecords' => 1,
            'showAllLocalizationLink' => 1
        ),
    ),
),

回答1:

The main problem is that IRRE relations of type 1:n work like this: A child record holds the uid of its parent. So your table tx_myext_domain_model_city holds the UID of your (imaginary) tx_myext_domain_model_address.

Therefore with the default configuration you will not be able to select a city multiple times as it can only have exactly one parent.

So you will need to use a relation table for this field. This table needs to contain a uid field for both the address (uid_address) and the city (uid_city):

CREATE TABLE tx_irreforeignselectordemo_address_city_mm (

    uid int(11) NOT NULL auto_increment,
    pid int(11) DEFAULT '0' NOT NULL,
    uid_address int(11) unsigned DEFAULT '0' NOT NULL,
    uid_city int(11) unsigned DEFAULT '0' NOT NULL,
    sorting int(11) unsigned DEFAULT '0' NOT NULL,

    PRIMARY KEY (uid),
    KEY parent (pid)
);

And it needs to have a TCA configuration for these fields (while the table itself can be hidden):

return array(
    'ctrl'      => array(
        'title'     => 'Relation table',
        'hideTable' => TRUE,
        'sortby'    => 'sorting',
    ),
    'columns'   => array(
        'uid_address' => Array(
            'label'  => 'Address',
            'config' => Array(
                'type'          => 'select',
                'foreign_table' => 'tx_irreforeignselectordemo_domain_model_address',
                'size'          => 1,
                'minitems'      => 0,
                'maxitems'      => 1,
            ),
        ),
        'uid_city'   => Array(
            'label'  => 'City',
            'config' => Array(
                'type'                => 'select',
                'foreign_table'       => 'tx_irreforeignselectordemo_domain_model_city',
                'foreign_table_where' => ' AND sys_language_uid IN (0,-1)',
                'size'                => 1,
                'minitems'            => 0,
                'maxitems'            => 1,
            ),
        ),
    ),
    'types'     => array(
        '0' => array('showitem' => 'uid_address,uid_city')
    ),
    'palettes'  => array()
);

You can then configure the TCA of your address to make it an IRRE field:

'type' => 'inline',
'foreign_table' => 'tx_yourext_address_city_mm',
'foreign_field' => 'uid_address',
'foreign_label' => 'uid_city',
'foreign_selector' => 'uid_city',
'foreign_unique' => 'uid_city',
'foreign_sortby' => 'sorting',

Note that foreign_unique tells TYPO3 that a city can only selected once.

And you need to define the relation from the other side (from your city TCA):

    'addresses' => array(
        'exclude' => 1,
        'label'   => 'Addresses',
        'config'  => array(
            'type' => 'inline',
            'foreign_table' => 'tx_irreforeignselectordemo_address_city_mm',
            'foreign_field' => 'uid_city',
            'foreign_label' => 'uid_address',
        ),
    ),

Once your configuration is complete, you will be able to use this in the Backend.

Since this is a non-standard MM relation, Extbase will not be able to deal with it by default. But we can compare this to the sys_file_reference table that was introduced in TYPO3 6. So we build an Extbase model for the CityRelation with the properties "address" and "city" and map this model to our mm table:

config.tx_extbase.persistence.classes {
    Visol\Irreforeignselectordemo\Domain\Model\CityRelation {
        mapping {
            tableName = tx_irreforeignselectordemo_address_city_mm
            columns {
                uid_address.mapOnProperty = address
                uid_city.mapOnProperty = city
            }
        }
    }
}

Now in our address model, we define the city (or cities - of you allow more than one choice) as ObjectStorage of type CityRelation:

/**
 * Cities
 *
 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Visol\Irreforeignselectordemo\Domain\Model\CityRelation>
 */
protected $cities = NULL;

We now have a property "cities" that contains the references to all selected cities. You can iterate through them and use them:

<f:for each="{address.cities}" as="cityRelation">
  <li>{cityRelation.city.name}</li>
</f:for>

Since I couldn't find an all-in-one demo for this and was interested in the topic, I created a demo extension that does what I just described - based on the Core and two extensions that deal with the topic: https://github.com/lorenzulrich/irreforeignselectordemo

The solution is an m:n approach anyway (because 1:n wouldn't work for the reasons stated above) so I decided to use "cities" instead of "city". While this might not make sense for selecting a city (as suggested by your post), it might make sense for other opportunities. Feel free to replace "cities" by "city" and set maxItems in the inline configuration to one - then you have kind of an 1:n.