ModifyTcaSelectFieldItemsEvent
\FGTCLB\ hands a
listener the items of a select field after the item provider of an academic
extension built them, so the listener can remove, add or reorder them. It
replaces the two ways a project had before: adding items in TCA, with no say
in their order, or replacing the whole
items of a field.
A site that does not offer theses removes that job type, a site that never shows rooms removes the room from the fields an editor can pick for a person list, and a project with a field of its own offers it in the same select.
This extension owns the event and dispatches it nowhere itself. The item providers of three academic extensions dispatch it, and each of them documents what it passes in.
When it is dispatched
The item provider of the field dispatches the event once each time the items of the field are built, after it added its own items. In the backend that is each time the record or the content element is opened in the form. The parameters a listener hands back are what the field offers.
| Extension | Table and field | Backend label, and where it is shown |
|---|---|---|
| academic_jobs | tx_, type and
employment_ | Job Type and Employment Type of a job record, the same two selects of the job form in the frontend, and Job Type of the job list, with the table and the field of the record |
| academic_persons | tt_, settings. and
settings., and
tx_, contract | Selected Contracts of the selected contracts plugin, Show only selected Fields of the list plugins, the selected profiles and the selected contracts, and Contract of a page contact of academic_contacts4pages |
| academic_contacts4pages | tx_,
email_, phone_ and physical_ | E-mail address, Phone number and Physical address of a page contact |
The job form of academic_jobs builds the options of its two selects in the frontend from the same item providers, so the event is dispatched there as well, and an item a listener removes is neither offered in the backend nor in the form.
What a listener reads and changes
| Method | What it does |
|---|---|
get | The parameters TYPO3 hands to an
items, with the
items the provider built, see below. |
set | Replaces them. The items of the array a listener passes are what the field offers, in that order. |
The parameters are those of an
items, see the TCA reference.
The keys a listener needs most:
| Key | Holds |
|---|---|
items | The items, each with label and value, and icon and
group where the field uses them. A label is a LLL:
reference or a text. See below for the form of an item. |
table | The table of the record. For a FlexForm field, the table of the
content element, tt_, unless the provider names the
record field it offers the items of, as the job list does. |
field | The field. For a FlexForm field, the name of the FlexForm
element, settings. for example, with the same
exception. |
row | The record being edited. For a FlexForm field, the values of the FlexForm. Empty in the job form, and declared optional, see the pitfalls below. |
flex | For a FlexForm field, the record of the content element. Missing for the field of a record, which tells the two apart where the table and the field are the same. |
site, effective | The site and the page the record belongs to. For a record outside
any site, on page 0 for example, site is a
\TYPO3\, not a
Site
. Call only the methods of
\TYPO3\ on it, or
check for
Site
first. site is
null
when the
caller has none, a form data group without site resolving on
TYPO3 v13, or a request without a site in the job form. |
inline, inline | The uid of the direct and of the topmost inline parent, when the
record is edited as an inline child. A parent that is not saved
yet has a NEW... string instead. For a record that is not
edited as an inline child, the common case, both are the empty
string ''. The site configuration passes
null
as
inline. Missing in the job form. Cast the
value, as
(int), and
use it only when it is above 0. |
inline, inline,
inline and the same keys of the topmost parent | The table, the field and the configuration of the inline parent.
'' and [] for a record that is not edited as an inline
child. Missing in the job form. |
TSconfig | The page TSconfig below
TCEFORM.<table>.<field>., or
null
when there is none. |
In the backend form, TYPO3 hands the items it built before the item provider
runs, the static items of the field and those of a foreign_, as
\TYPO3\ objects, on TYPO3 v13
and v14 alike. The items a provider adds are arrays, and the job form of the
frontend hands every item over as an array. A
Select implements
\Array, so a closure typed
array that reads
$item works for both.
An item is therefore not always an array. A closure typed
array
alone
raises a
\Type in the backend form for a field with static items.
The event declares these parameters once, as the PHPStan type alias
Items, and the item providers import it. A listener
or an item provider of a project imports it the same way, so static analysis
checks it against the values above:
/**
* @phpstan-import-type ItemsProcFuncParameters from \FGTCLB\AcademicBase\Event\ModifyTcaSelectFieldItemsEvent
*/
final class MyItemsProcFunc
{
/**
* @param ItemsProcFuncParameters $parameters
*/
public function itemsProcFunc(array &$parameters): void {}
}
Example
A site does not offer theses on its job board and never shows the room of a person. One listener removes both items. It checks the table and the field first, because it is called for every field of the table above:
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use FGTCLB\AcademicBase\Event\ModifyTcaSelectFieldItemsEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Schema\Struct\SelectItem;
final readonly class RemoveUnusedSelectItems
{
/**
* The values to remove, by table and field.
*/
private const REMOVED_VALUES = [
'tx_academicjobs_domain_model_job' => [
'type' => ['3'],
],
'tt_content' => [
'settings.showFields' => ['contracts.room'],
],
];
#[AsEventListener(identifier: 'my-extension/remove-unused-select-items')]
public function __invoke(ModifyTcaSelectFieldItemsEvent $event): void
{
$parameters = $event->getParameters();
$removedValues = self::REMOVED_VALUES[$parameters['table']][$parameters['field']] ?? [];
if ($removedValues === []) {
return;
}
$parameters['items'] = array_values(array_filter(
$parameters['items'],
static fn(array|SelectItem $item): bool => !in_array((string)($item['value'] ?? ''), $removedValues, true),
));
$event->setParameters($parameters);
}
}
The value is compared as a string, because the item providers declare some values as integers and the backend hands others over as strings.
Rules and pitfalls
- Check table and field. One listener is called for every field of the table above. A listener that does not check both changes all of them.
- No record in the frontend. In the job form, the parameters carry
items,config,TSconfig,table,row,field,effectiveandPid site, and none of the keys of an inline parent.rowis an empty array there, because the job does not exist yet. The event declaresrowoptional, so static analysis reports a listener that reads it without a fallback. Read it as$parameters, and every field of it with a fallback too, as['row'] ?? [] $row.['pid'] ?? $parameters ['effective Pid'] - The inline uids are not always integers. A listener that passes
$parametersto a parameter typed['inline Parent Uid'] intraises a\Typefor every record that is not edited as an inline child, because the value isError ''there. Cast it first. - A FlexForm field has two rows.
rowholds the values of the FlexForm, the content element isflex.Parent Database Row - Keep the stored values in mind. Removing an item hides it from the form, it does not change the records that store it already. A single select shows such a value as an invalid value. A multiple select, such as Show only selected Fields, no longer shows it, and the next save of the record drops it.
- Keep the list a list. Pass the items back with consecutive keys, with
array_after a filter, so the order is the one the backend shows.values () - No order is promised between several listeners. A listener that has
to run after another one says so with the
afterargument of#.[As Event Listener]
Introduced in
The event exists since version 2.1. The extensions that dispatch it describe in their own changelogs when they started to, for example academic_jobs 2.1 and academic_persons 2.4.