Skip to content

Instantly share code, notes, and snippets.

@smichaelsen
Last active April 3, 2023 03:14
Show Gist options
  • Save smichaelsen/4e99a0d19d81a42caeac to your computer and use it in GitHub Desktop.
Save smichaelsen/4e99a0d19d81a42caeac to your computer and use it in GitHub Desktop.
TCA bidirectional mm relation to same field

I have a bit of TCA headache. I want to configure two records of the same table as combinable, so I have a field that let's you select records of the same table, but i need that bidirectional.

If I edit record #1 and select #2 as combinable, then I expect #1 selected when I open record #2.

I'm aware of bidirectional mm relations, but it seems that it won't work if both sides are handled by the same table AND the same field.

Any ideas?

UPDATE: I've attached a possible solution. Unfortunatelly it works by replacing the TYPO3 RelationHandler.

    // ... TCA
    'combinable_adventures' => [
        'label' => $lll . '.combinable_adventures',
        'config' => [
            'type' => 'select',
            'foreign_table' => 'tx_tripshop_domain_model_adventure',
            'foreign_table_where' => ' AND tx_tripshop_domain_model_adventure.tour = ###REC_FIELD_tour### AND tx_tripshop_domain_model_adventure.uid <> ###THIS_UID###',
            'foreign_sortby' => 'sorting',
            'MM' => 'tx_tripshop_domain_model_adventure_combinations',
            'MM_opposite_field' => 'combinable_adventures',
            'maxitems' => 99,
        ],
    ],
<?php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Core\Database\RelationHandler::class] = [
'className' => \Smichaelsen\Gist\Database\RelationHandler::class,
];
<?php
namespace Smichaelsen\Gist\Database;
/**
* @see https://gist.github.com/smichaelsen/4e99a0d19d81a42caeac
* As of TYPO3 7 it's not possible in TCA to define bidirectional MM relations with each side of
* the relation being the same field. (e.g. "related news").
*
* As a fix this extension of the relation handler just stores the relation in both ways into the
* MM table when it detects a MM relation with both sides being the same field.
*/
class RelationHandler extends \TYPO3\CMS\Core\Database\RelationHandler
{
/**
* @var bool
*/
protected $isMmRelationToSameField = false;
/**
* @param string $itemlist
* @param string $tablelist
* @param string $MMtable
* @param int $MMuid
* @param string $currentTable
* @param array $conf
*/
public function start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, $currentTable = '', $conf = array())
{
parent::start($itemlist, $tablelist, $MMtable, $MMuid, $currentTable, $conf);
// We don't have the current field name available here but we assume
// that if the MM relation goes to the same table and the target field has
// the same config than our current config that the target field *is* our current field
if (
isset($this->MM_oppositeFieldConf) &&
$this->MM_oppositeFieldConf === $conf &&
$this->MM_oppositeTable === $this->firstTable
) {
$this->isMmRelationToSameField = true;
}
}
/**
* @param string $MM_tableName
* @param int $uid
* @param bool $prependTableName
*/
public function writeMM($MM_tableName, $uid, $prependTableName = false)
{
parent::writeMM($MM_tableName, $uid, $prependTableName);
// if the relation goes to the same field we write each relation for a 2nd time
// into the database while switching local and foreign.
if ($this->isMmRelationToSameField) {
$backupItemArray = $this->itemArray;
$this->itemArray = [['id' => $uid, 'table' => $this->firstTable]];
foreach ($backupItemArray as $relation) {
parent::writeMM($MM_tableName, $relation['id']);
// This does not set the correct amount and there is no mechanism to set the relation
// to 0 again, but it's good enough for extbase to work with.
$this->getDatabaseConnection()->exec_UPDATEquery(
$this->firstTable,
'uid = ' . (int)$relation['id'],
[
$this->MM_oppositeField => 1,
]
);
}
$this->itemArray = $backupItemArray;
}
}
}
Copy link

ghost commented Apr 12, 2021

Thanks for this @smichaelsen

TYPO3 v10 version:

<?php

namespace Smichaelsen\Gist\Database;

/**
 * @see https://gist.github.com/smichaelsen/4e99a0d19d81a42caeac
 * As of TYPO3 7 it's not possible in TCA to define bidirectional MM relations with each side of
 * the relation being the same field. (e.g. "related news").
 *
 * As a fix this extension of the relation handler just stores the relation in both ways into the
 * MM table when it detects a MM relation with both sides being the same field.
 */
class RelationHandler extends \TYPO3\CMS\Core\Database\RelationHandler
{
    protected bool $isMmRelationToSameField = false;

    /**
     * @param string $itemlist
     * @param string $tablelist
     * @param string $MMtable
     * @param int $MMuid
     * @param string $currentTable
     * @param array $conf
     */
    public function start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, $currentTable = '', $conf = array())
    {
        parent::start($itemlist, $tablelist, $MMtable, $MMuid, $currentTable, $conf);

        // We don't have the current field name available here but we assume
        // that if the MM relation goes to the same table and the target field has
        // the same config than our current config that the target field *is* our current field
        if (
            isset($this->MM_oppositeFieldConf) &&
            $this->MM_oppositeFieldConf === $conf &&
            $this->MM_oppositeTable === $this->firstTable
        ) {
            $this->isMmRelationToSameField = true;
        }
    }

    /**
     * @param string $MM_tableName
     * @param int $uid
     * @param bool $prependTableName
     */
    public function writeMM($MM_tableName, $uid, $prependTableName = false)
    {
        parent::writeMM($MM_tableName, $uid, $prependTableName);

        // if the relation goes to the same field we write each relation for a 2nd time
        // into the database while switching local and foreign.
        if ($this->isMmRelationToSameField) {
            $backupItemArray = $this->itemArray;
            $this->itemArray = [['id' => $uid, 'table' => $this->firstTable]];

            foreach ($backupItemArray as $relation) {
                parent::writeMM($MM_tableName, $relation['id']);

                // This does not set the correct amount and there is no mechanism to set the relation
                // to 0 again, but it's good enough for extbase to work with.

                $this->getConnectionForTableName('YOUR_TABLE_NAME_GOES_HERE')->update(
                    $this->firstTable,
                    [$this->MM_oppositeField => 1],
                    ['uid' => (int)$relation['id']],
                );
            }

            $this->itemArray = $backupItemArray;
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment