What am I missing to be able to save a domain object in a property of another domain object in TYPO3?

After submitting my form in the frontend of my extension a value for the trainer and job properties of my employee object are sent to my controller action. However only the trainer is handled correctly. When I debug the trainer and job in my controller action trainer is debugged as an object with properties i.e. an employee with firstname, lastname etc. while the job is debugged as NULL.

At the end of my controller action i update my employee object and redirect to the view where the form was submitted - passing the employee as an argument.

In this view i debug the employee with fluids f:debug - there the job is debugged as a string like ‘My\Extension\Domain\Model\TraineeJob:2’. - The trainer is still debugged as an object with properties.

Since both trainer and job are models in my extension I thought it would work the same way - though I might be missing something simple.

My typo3 version is 11.5.24.

You will find my code down below - but first screenshots of my debugs:

Debugging the employee object in the controller:
Debugging $this->request->getArguments()

Debugging the employee with my fluid template after the employee was updated in the controller:

Here is my code:

EmployeeController.php

public function updateTraineeSettingsAction(\Reports\TraineeReports\Domain\Model\Employee $employee) 
{
    debug($this->request->getArguments());
    debug($employee);
    die();
    $this->employeeRepository->update($employee);
    $this->redirect('reportManagement', null, null, ['employee' => $employee]);
}

TraineeSettingsForm.html

<f:form action="updateTraineeSettings" name="employee" arguments="{employee : employee}" object="{employee}">
  <div class="modal-header">
    <h1 class="modal-title fs-5" id="staticBackdropLabel">Einstellungen</h1>
    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
  </div>
  <div class="modal-body">

    <div class="form-floating my-2">
      <f:form.select property="trainer" class="form-select" value="{employee.trainer}">
        <f:for each="{trainers}" as="trainer">
          <f:if condition="{trainer.uid} == {employee.trainer.uid}">
            <f:then>
              <option value="{trainer.uid}" selected>{trainer.lastName}, {trainer.firstName}</option>
            </f:then>
            <f:else>
              <option value="{trainer.uid}">{trainer.lastName}, {trainer.firstName}</option>
            </f:else>
          </f:if>
        </f:for>
      </f:form.select>
      <label for="trainer">Ausbilder</label>
    </div>

    <div class="form-floating my-2">
      <f:form.select property="job" class="form-select" value="{employee.job.uid}">
        <f:for each="{jobs}" as="job">
          <f:if condition="{job.uid} == {employee.job.uid}">
            <f:then>
              <option value="{job.uid}" selected>{job.title}</option>
            </f:then>
            <f:else>
              <option value="{job.uid}">{job.title}</option>
            </f:else>
          </f:if>
        </f:for>
      </f:form.select>
      <label for="jobTitle">Ausbildungsberuf</label>
    </div>
  </div>
  <div class="modal-footer">
    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
    <button type="submit" class="btn btn-success">Speichern</button>
  </div>
</f:form>

tx_traineereports_domain_model_traineejob.php

<?php
return [
    'ctrl' => [
        'title' => 'LLL:EXT:test/Resources/Private/Language/locallang_db.xlf:tx_traineereports_domain_model_traineejob',
        'label' => 'title',
        'tstamp' => 'tstamp',
        'crdate' => 'crdate',
        'cruser_id' => 'cruser_id',
        'versioningWS' => true,
        'languageField' => 'sys_language_uid',
        'transOrigPointerField' => 'l10n_parent',
        'transOrigDiffSourceField' => 'l10n_diffsource',
        'delete' => 'deleted',
        'enablecolumns' => [
            'disabled' => 'hidden',
            'starttime' => 'starttime',
            'endtime' => 'endtime',
        ],
        'searchFields' => 'title',
        'iconfile' => 'EXT:test/Resources/Public/Icons/tx_traineereports_domain_model_traineejob.gif'
    ],
    'types' => [
        '1' => ['showitem' => 'title, training_plan, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime'],
    ],
    'columns' => [
        'sys_language_uid' => [
            'exclude' => true,
            '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' => 'select',
                'renderType' => 'selectSingle',
                'default' => 0,
                'items' => [
                    ['', 0],
                ],
                'foreign_table' => 'tx_traineereports_domain_model_traineejob',
                'foreign_table_where' => 'AND {#tx_traineereports_domain_model_traineejob}.{#pid}=###CURRENT_PID### AND {#tx_traineereports_domain_model_traineejob}.{#sys_language_uid} IN (-1,0)',
            ],
        ],
        'l10n_diffsource' => [
            'config' => [
                'type' => 'passthrough',
            ],
        ],
        'hidden' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
            'config' => [
                'type' => 'check',
                'renderType' => 'checkboxToggle',
                'items' => [
                    [
                        0 => '',
                        1 => '',
                        'invertStateDisplay' => true
                    ]
                ],
            ],
        ],
        'starttime' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
            'config' => [
                'type' => 'input',
                'renderType' => 'inputDateTime',
                'eval' => 'datetime,int',
                'default' => 0,
                'behaviour' => [
                    'allowLanguageSynchronization' => true
                ]
            ],
        ],
        'endtime' => [
            'exclude' => true,
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
            'config' => [
                'type' => 'input',
                'renderType' => 'inputDateTime',
                'eval' => 'datetime,int',
                'default' => 0,
                'range' => [
                    'upper' => mktime(0, 0, 0, 1, 1, 2038)
                ],
                'behaviour' => [
                    'allowLanguageSynchronization' => true
                ]
            ],
        ],

        'title' => [
            'exclude' => true,
            'label' => 'LLL:EXT:test/Resources/Private/Language/locallang_db.xlf:tx_traineereports_domain_model_traineejob.title',
            'config' => [
                'type' => 'input',
                'size' => 30,
                'eval' => 'trim,required',
                'default' => ''
            ],
        ],
        'training_plan' => [
            'exclude' => true,
            'label' => 'LLL:EXT:test/Resources/Private/Language/locallang_db.xlf:tx_traineereports_domain_model_traineejob.training_plan',
            'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
                'training_plan',
                [
                    'appearance' => [
                        'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.addFileReference'
                    ],
                    'overrideChildTca' => [
                        'types' => [
                            '0' => [
                                'showitem' => '
                                --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
                                --palette--;;filePalette'
                            ],
                            \TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [
                                'showitem' => '
                                --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
                                --palette--;;filePalette'
                            ],
                            \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [
                                'showitem' => '
                                --palette--;;imageoverlayPalette,
                                --palette--;;filePalette',
                            ],
                            \TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [
                                'showitem' => '
                                --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
                                --palette--;;filePalette'
                            ],
                            \TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [
                                'showitem' => '
                                --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
                                --palette--;;filePalette'
                            ],
                            \TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [
                                'showitem' => '
                                --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
                                --palette--;;filePalette'
                            ]
                        ],
                    ],
                    'foreign_match_fields' => [
                        'fieldname' => 'training_plan',
                        'tablenames' => 'tx_traineereports_domain_model_traineejob',
                        'table_local' => 'sys_file',
                    ],
                    'maxitems' => 1,
                    'minitems' => 1
                ]
            ),
            
        ],
    
    ],
];

\Classes\Domain\Model\TraineeJob.php

<?php

declare(strict_types=1);

namespace Reports\TraineeReports\Domain\Model;


/**
 * This file is part of the "reports" Extension for TYPO3 CMS.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * (c) 2023 
 */

/**
 * TraineeJob
 */
class TraineeJob extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
    /**
     * title
     *
     * @var String
     */
    protected $title = null;


    /**
     * Returns the title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Sets the title
     *
     * @param string $title
     * @return void
     */
    public function setTitle(string $title)
    {
        $this->title = $title;
    }

    /**
     * trainingPlan
     *
     * @var \TYPO3\CMS\Extbase\Domain\Model\FileReference
     */
    protected $trainingPlan = null;

    /**
     * Returns the trainingPlan
     *
     * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference
     */
    public function getTrainingPlan()
    {
        return $this->trainingPlan;
    }

    /**
     * Sets the trainingPlan
     *
     * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $trainingPlan
     * @return void
     */
    public function setTrainingPlan(\TYPO3\CMS\Extbase\Domain\Model\FileReference $trainingPlan)
    {
        $this->trainingPlan = $trainingPlan;
    }
}

ext_tables.sql

CREATE TABLE tx_itonboarding_domain_model_employee (
    training_year int(11) NOT NULL DEFAULT '0',
    birthday date DEFAULT NULL,
    place_of_birth varchar(255) NOT NULL DEFAULT '',
    address varchar(255) NOT NULL DEFAULT '',
    job int(11) unsigned DEFAULT '0',
    company varchar(255) NOT NULL DEFAULT '',
    start_of_training date DEFAULT NULL,
    trainer int(11) unsigned DEFAULT '0',
    reports int(11) unsigned NOT NULL DEFAULT '0',
    tx_extbase_type varchar(255) NOT NULL DEFAULT ''
);

CREATE TABLE tx_traineereports_domain_model_report (
    employee int(11) unsigned DEFAULT '0' NOT NULL,
    date date DEFAULT NULL,
    training_year int(11) NOT NULL DEFAULT '0',
    text text NOT NULL DEFAULT '',
    training_plan_activity varchar(255) NOT NULL DEFAULT '',
    is_to_be_accepted smallint(1) unsigned NOT NULL DEFAULT '0',
    is_accepted smallint(1) unsigned NOT NULL DEFAULT '0',
    is_denied smallint(1) unsigned NOT NULL DEFAULT '0',
    deny_reason varchar(255) NOT NULL DEFAULT '',
    attachment int(11) unsigned NOT NULL DEFAULT '0',
    images int(11) unsigned NOT NULL DEFAULT '0',
    parent_signature int(11) unsigned NOT NULL DEFAULT '0',
);

CREATE TABLE fe_users (
    employee int(11) unsigned DEFAULT '0',
);

CREATE TABLE tx_traineereports_domain_model_traineejob (
    title varchar(255) NOT NULL DEFAULT '',
    training_plan int(11) unsigned NOT NULL DEFAULT '0',
);

I tried to log my form content with the javascript function serialize() and noticed that trainer and job had a difference - trainer has [_identity] and job doesn’t - I have not found out why that is missing or what causes this:

tx_traineereports_traineereports%5Bemployee%5D%5B__identity%5D=414& tx_traineereports_traineereports%5B__referrer%5D%5B%40extension%5D=TraineeReports&tx_traineereports_traineereports%5B__referrer%5D%5B%40controller%5D=Employee&tx_traineereports_traineereports%5B__referrer%5D%5B%40action%5D=reportManagement&

tx_traineereports_traineereports%5B__referrer%5D%5B%40request%5D=%7B%22%40extension%22%3A%22TraineeReports%22%2C%22%40controller%22%3A%22Employee%22%2C%22%40action%22%3A%22reportManagement%22%7D138bd55a3f3091041977cdd8fad23ec57fc4d813&

tx_traineereports_traineereports%5B__trustedProperties%5D=%7B%22employee%22%3A%7B%22trainingYear%22%3A1%2C%22trainer%22%3A%7B%22__identity%22%3A1%7D%2C%22birthday%22%3A1%2C%22placeOfBirth%22%3A1%2C%22address%22%3A1%2C%22job%22%3A1%2C%22company%22%3A1%2C%22startOfTraining%22%3A1%2C%22__identity%22%3A1%7D%7D4240a2fd9cb3ee9752532dbb38449e72334a79b0&

tx_traineereports_traineereports%5Bemployee%5D%5Btrainer%5D%5B__identity%5D=293& tx_traineereports_traineereports%5Bemployee%5D%5Bjob%5D=2&

Removed all configuration regarding the traineeJob Model.
Redid everything by having a new extension made with extension builder as a reference now it works.

My bad - was probably overlooking some small details.

1 Like