Breaking: #110485 - Menu content object registration changed
See forge#110485
Description
Menu content objects - the sub types of the
HMENU
content
object, for example
TMENU
- are now registered as tagged
services via dependency injection. This comes with the following breaking
changes:
- The class
\TYPO3\has been removed. It was markedCMS\ Frontend\ Content Object\ Menu\ Menu Content Object Factory @internal, but its methodregisterwas the only way to add or override a menu type and was therefore used fromMenu Type () ext_localconf.php. - The exception
\TYPO3\has been removed. An unregistered menu type is no longer signaled by an exception, the menu is simply not rendered - which is the behavior that was already visible from the outside, since all Core call sites caught the exception and continued silently.CMS\ Frontend\ Content Object\ Menu\ Exception\ No Such Menu Type Exception \TYPO3\now declares a constructor which requires the service locator of all registered menu content objects as the first argument. Subclasses which declare their own constructor must hand this argument toCMS\ Frontend\ Content Object\ Menu\ Abstract Menu Content Object parent::__.construct () - Menu content objects are never shared: every
HMENUlevel is rendered by its own instance. Instantiating them throughGeneralis not supported anymore, they have to be retrieved from the service locator.Utility:: make Instance ()
The constructor argument of
Abstract is set by the
dependency injection container for all services tagged as
frontend., together with
shared: false
.
Registering a menu type therefore only requires the tag itself.
Impact
Menu types registered via
Menu in
ext_localconf.php cause a fatal PHP error, since the class does
not exist anymore.
Custom classes extending
Abstract with their own
constructor cause a fatal PHP error as soon as they are instantiated,
unless they pass the service locator on to
parent::__.
Affected installations
All installations with custom extensions registering their own menu type, or
overriding the
TMENU
implementation. Since the only menu type
shipped by TYPO3 Core is
TMENU
, most installations are not
affected. The extension scanner reports usages of
Menu and
register.
Migration
Remove the
register call from ext_localconf.php
and register the menu content object as a tagged service instead. The
identifier is the menu type as used in TypoScript and has to be
written in upper case:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
My\Extension\ContentObject\Menu\FancyMenuContentObject:
tags:
- name: frontend.menucontentobject
identifier: 'FANCYMENU'
The menu type can then be used in TypoScript as before:
page.10 = HMENU
page.10 {
1 = FANCYMENU
1 {
NO = 1
}
}
An existing menu type - including
TMENU
- is overridden by
registering a custom class with the same identifier. As extensions
are loaded after TYPO3 Core, the last registration for an identifier wins.
Custom classes extending
Abstract which declare
their own constructor have to accept and pass on the service locator:
use Psr\Container\ContainerInterface;
use TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject;
final class FancyMenuContentObject extends AbstractMenuContentObject
{
public function __construct(
ContainerInterface $menuContentObjectLocator,
private readonly MyOwnDependency $myOwnDependency,
) {
parent::__construct($menuContentObjectLocator);
}
}
Code which created a menu content object through
Menu should inject the
locator of all registered menu content objects instead:
services:
My\Extension\DataProcessing\MyMenuProcessor:
shared: false
arguments:
$menuContentObjectLocator: !tagged_locator { tag: 'frontend.menucontentobject', index_by: 'identifier' }
use Psr\Container\ContainerInterface;
final class MyMenuProcessor
{
public function __construct(
private readonly ContainerInterface $menuContentObjectLocator,
) {}
public function process(): void
{
$menu = $this->menuContentObjectLocator->get('TMENU');
// ...
}
}