Search code examples
typo3typo3-11.xtca

TYPO3 11 TCA field 'type' => 'user' - Fetch child objects like inline


I'm trying to create my own TCA field user type to simply display some data in the typo3 backend of an objects child/children.

The thing is that when I fetch the children by query builder I would be returned an array with id's for its children instead of e.g. the title as defined in the tca of that object. As I want to display not the id but e.g. the title of that childs child I am stuck with a problem I don't know how to solve.

I've tried looking at the classes for the select elements (inline too) but when copying the beginning of the render function of it I already run into an "array key not defined" error for "items" (which is optional if a foreign_table is defined). The class I copied it from was \TYPO3\CMS\Backend\Form\Element\SelectSingleElement which I found by searching for AbstractFormElement which my class extends from too.

To make it clearer on what I want to achieve there's an example (I only used the parts that are important):

object1_tca.php:

'columns' => [
    'object1' => [
        'label' => 'object1',
        'config' => [
            'type' => 'user',
            'renderType' => 'showChildData',
            'foreign_table' => 'tx_ext_domain_model_object2',
            'foreign_field' => 'object1',
            'render_settings' => [
                'childFields' => 'object3'
            ],
        ],
    ]
]

object2_tca.php:

'ctrl' => [
    'title' => 'object2',
    'label' => 'name',
    'label_alt' => 'object3',
    'label_alt_force' => true,
],
'columns' => [
    'object3' => [
        'label' => 'object3',
        'config' => [
            'type' => 'select',
            'renderType' => 'selectSingle',
            'foreign_table' => 'tx_ext_domain_model_object3',
            'foreign_field' => 'object2',
        ],
    ],
]

object3_tca.php:

'ctrl' => [
    'title' => 'object3',
    'label' => 'name',
]

So the objects relations are as follows:

------------                 ------------
| object 1 | ----- 1:m ----→ | object 2 |
------------                 ------------

------------                 ------------
| object 2 | ----- m:1 ----→ | object 3 |
------------                 ------------

So, object2 will display as title its own name and the name of object3 that is selected. If I would change the field object2 of object1 configuration to a single select, I would also get that title for that child.

But I don't know how to fetch that title or that object2 data in my user type field.

In the end I want to do more with the data, group them by e.g. some field aso. This should also be applicable for different objects, so it's not the same repository or objects for the fields I want to use them for.

Can anyone help me? Is this even possible without having to fetch the children and the childrens children aso. from the database?

If you need more information I will provide whatever I can :) Any help is welcome. I feel like this should be easy but I'm stuck with even knowing where to look anymore. Thanks.


Solution

  • I've struggled with fetching the data when actually it would have been quite easy. As I've made mistakes along the way I want to share this first:

    Do not use EXTBASE to approach this matter. I've tried accessing the childrens data by using DataMapper which is an extbase class and should not be used for TCA related content (TYPO3 slack was very helpful here).

    There is a much simpler way to do this: Use the global TCA array ($GLOBALS['TCA']) to fetch the configuration/the data from a childs child.

    With this I formed the function below:

    private function createFieldConfigArray($foreign_table, $fieldConfig) {
        $fieldConfigArray = [];
    
        if (strpos($fieldConfig, '.') !== false) {
            $fieldData = explode('.', $fieldConfig);
            $fieldName = $fieldData['0'];
    
            array_shift($fieldData);
        }
        else {
            $fieldName = $fieldConfig;
        }
    
        $tcaFieldConfig = $GLOBALS['TCA'][$foreign_table]['columns'][$fieldName]['config'];
    
        if (isset($tcaFieldConfig['foreign_table'])) {
            if (!isset($tcaFieldConfig['foreign_field'])) {
                throw new \Exception("Invalid tca configuration for field $fieldName in table $foreign_table. Missing 'foreign_field' in config.", 1);
            }
    
            $fieldConfigArray = [
                $fieldName => [
                    'table' => $tcaFieldConfig['foreign_table'],
                    'foreign_field' => $tcaFieldConfig['foreign_field'],
                    'childField' => $this->createFieldConfigArray($tcaFieldConfig['foreign_table'], implode('.', $fieldData))
                ]
            ];
    
            if (isset($tcaFieldConfig['MM'])) {
                $fieldConfigArray = [
                    'MM' => $tcaFieldConfig['MM']
                ];
    
                if (isset($tcaFieldConfig['foreign_selector'])) {
                    $fieldConfigArray = [
                        'foreign_selector' => $tcaFieldConfig['foreign_selector']
                    ];
                }
                else {
                    $fieldConfigArray = [
                        'foreign_selector' => 'uid_local'
                    ];
                }
            }
        }
        else {
            $fieldConfigArray = [
                $fieldName => []
            ];
        }
    
        return $fieldConfigArray;
    }
    

    With this I'm fetching the child property config from TCA to further work with it:

    private function getFieldValueFromConfig($childRecord, $fieldConfig) {
        $fieldValue = '';
    
        foreach ($fieldConfig as $fieldName => $config) {
            $child = $childRecord[$fieldName];
    
            if (isset($config['table'])) {
                if (isset($config['MM'])) {
                    $mmForeignField = $config['foreign_selector'] === 'uid_local' ? 'uid_foreign' : 'uid_local';
                    $childRecords = $this->getMMChildDataFromDatabase($config['mm'], $config['foreign_selector'], $mmForeignField, $config['table'], $child);
                }
                else {
                    $childRecord = $this->getChildDataFromDatabase($config['table'], 'uid', $child);
    
                    if (count($childRecord) > 1) {
                        throw new \Exception("Multiple records with uid $child found! Result: " . print_r($childRecord), 1);
                    }
                    else {
                        $childRecord = $childRecord[0];
                    }
    
                    $fieldValue = $this->getFieldValueFromConfig($childRecord, $config['childField']);
                }
            }
            else {
                $fieldValue = $child;
            }
        }
    
        return $fieldValue;
    }
    

    These are both recursive functions (improvements not made yet) which will fetch childrens data down the line until it arrives at the last given property. Given the config below:

    'exam_series_grade' => [
            'config' => [
                'type' => 'user',
                'renderType' => 'showChildData',
                'foreign_table' => 'tx_someext_domain_model_somemodel',
                'foreign_field' => 'parent_identifier_field',
                'render_settings' => [
                    'childFields' => 'submodel.name, some_property',
                    'displayString' => 'Label 1: {$submodel.name}, Label 2: {$some_property}',
                ],
            ],
        ],
    

    I would be returned a string "Label 1: submodels name, Label 2: some value" for each child. I'll improve this for mm relations too. But in the end I learned how to access child data in TCA without using EXTBASE.

    I post this here in the hopes that if someone might struggle with the same kind of problem of accessing data in TCA you'll find the approach here. Cheers.