Feature: #107823 - ComponentFactory for backend components
See forge#107823
Description
A new
\TYPO3\
class has been introduced as the central location for all backend component
creation. It provides factory methods for buttons and menu components,
offering both pre-configured buttons for common patterns and basic component
creation methods.
The
Component serves multiple purposes:
- Pre-configured common buttons – Ready-to-use buttons like back, close, save, reload, and view with standardized icons, labels, and behavior.
- Basic button creation – Factory methods for creating button
instances (previously available only on
Button).Bar - Menu component creation – Factory methods for creating Menu and
MenuItem instances (previously available only on
MenuandRegistry Menu).
The deprecated
Button,
Menu::,
and
Menu methods have been replaced by
Component, providing a cleaner separation of concerns where
container classes manage organization and
Component handles
component creation.
Additionally, several "add" methods now support fluent interface patterns to enable method chaining for improved code readability.
Database record list and file list
The
\TYPO3\ and
\TYPO3\ classes, which are responsible in the
backend to show all lists of records and files, now make use of the
ComponentFactory. The list of "action buttons" is no longer represented with
plain HTML and allows a clear distinction of primary and secondary actions.
For this, the new enum
\TYPO3\ and
\TYPO3\ are also added to
the Button API to allow this grouping.
The existing PSR-14 events (
\TYPO3\,
\TYPO3\) which are used
within these classes have been streamlined to deal with these API changes.
Details can be found in the related breaking changes document of forge#107884.
The Button API has also been enhanced to allow passing a new
\TYPO3\ enum to
differentiate the buttons for certain icon sizes.
Available Factory Methods
The
Component provides two categories of methods:
Pre-configured common buttons:
These methods provide ready-to-use buttons with sensible defaults. The
returned instances are fully mutable and can be further customized using
fluent interface methods (for example,
set,
set,
set).
URL parameters accept both
string and
Uri for
convenience.
create– Standard back navigation with "Go back" label.Back Button (string |Uri Interface $return Url) create– Close button for modal-like views.Close Button (string |Uri Interface $close Url) create– Standard save button for forms.Save Button (string $form Name = '') create– Reload current view.Reload Button (string |Uri Interface $request Uri) create– View/preview page button with data attributes.View Button (array $preview Data Attributes = [])
Basic button creation:
create– Creates a new LinkButton instance.Link Button () create– Creates a new InputButton instance.Input Button () create– Creates a new GenericButton instance.Generic Button () create– Creates a new SplitButton instance.Split Button () create– Creates a new DropDownButton instance.Drop Down Button () create– Creates a new FullyRenderedButton instance.Fully Rendered Button () create– Creates a new ShortcutButton instance.Shortcut Button () create– Creates a new DropDownDivider instance.Drop Down Divider () create– Creates a new DropDownItem instance.Drop Down Item ()
Note
All button instances extending the
Abstract class support
arbitrary tag attributes via
set.
Menu component creation:
create– Creates a new Menu instance.Menu () create– Creates a new MenuItem instance.Menu Item ()
Note
The corresponding
Button,
Menu::, and
Menu methods
are now deprecated. See Deprecation: #107823 - ButtonBar, Menu, and MenuRegistry make* methods deprecated.
The Button API has also been enhanced to allow passing a new enum to differentiate the buttons for certain icon sizes.
Improvements to Button API types
The following button types can use
get and
set
methods in their instance to set the icon size with the
\TYPO3\ enum,
choosing between a small and medium variant (utilizing CSS classes
internally):
\TYPO3\CMS\ Backend\ Template\ Components\ Buttons\ Drop Down Button \TYPO3\CMS\ Backend\ Template\ Components\ Buttons\ Generic Button \TYPO3\CMS\ Backend\ Template\ Components\ Buttons\ Link Button
Impact
Backend module developers should now inject
Component in their
controllers to create buttons. The factory provides:
- Pre-configured buttons for common patterns (back, close, save, reload, view).
- Basic button creation methods (formerly only available on
Button).Bar
The
Button methods continue to work but are deprecated and
will be removed in TYPO3 v15. This change provides a cleaner architecture where
Component
handles all button creation and
Button focuses
solely on managing button positioning and organization.
Example – Using pre-configured buttons (inject ComponentFactory):
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
// In controller constructor
public function __construct(
protected readonly ComponentFactory $componentFactory,
) {}
// In controller action
public function editAction(): ResponseInterface
{
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
// Use pre-configured back button
$backButton = $this->componentFactory->createBackButton($returnUrl);
$buttonBar->addButton($backButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
// Use pre-configured save button
$saveButton = $this->componentFactory->createSaveButton('editform');
$buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
// ...
}
Example – Creating basic buttons via
Component:
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
// Inject ComponentFactory in constructor
public function __construct(
protected readonly ComponentFactory $componentFactory,
) {}
// Create basic buttons via factory
$linkButton = $this->componentFactory->createLinkButton()
->setHref($url)
->setTitle('Custom')
->setIcon($icon);
Example - Using the ModuleTemplate convenience method:
public function __construct(
protected readonly ComponentFactory $componentFactory,
) {}
public function myAction(): ResponseInterface
{
// Shorthand: use ModuleTemplate::addButtonToButtonBar()
$this->moduleTemplate->addButtonToButtonBar(
$this->componentFactory->createReloadButton(
$this->request->getUri()->getPath()
),
ButtonBar::BUTTON_POSITION_RIGHT
);
$this->moduleTemplate->addButtonToButtonBar(
$this->componentFactory->createBackButton($returnUrl),
ButtonBar::BUTTON_POSITION_LEFT,
1
);
// ...
}
Example - Customizing pre-configured buttons:
public function __construct(
protected readonly ComponentFactory $componentFactory,
) {}
public function myAction(): ResponseInterface
{
// Pre-configured buttons return mutable instances that can be further
// customized.
$reloadButton = $this->componentFactory
->createReloadButton(
(string)$this->uriBuilder->buildUriFromRoute(
$currentModule->getIdentifier()
)
)
->setDataAttributes(['csp-reports-handler' => 'refresh']);
$this->moduleTemplate->addButtonToButtonBar(
$reloadButton,
ButtonBar::BUTTON_POSITION_RIGHT
);
// Add custom styling or behavior to a save button
$saveButton = $this->componentFactory
->createSaveButton('myform')
->setClasses('btn-primary custom-save')
->setDataAttributes(['validate' => 'true']);
$this->moduleTemplate->addButtonToButtonBar(
$saveButton,
ButtonBar::BUTTON_POSITION_LEFT
);
// URL parameters accept both string and UriInterface
$backButton = $this->componentFactory->createBackButton(
$this->request->getUri()
); // UriInterface
// or
$backButton = $this->componentFactory->createBackButton(
'/return/url'
); // string
// ...
}
Fluent Interface Improvements
Several "add" methods now support fluent interface patterns, enabling method chaining:
ButtonBar
$buttonBar
->addButton($backButton, ButtonBar::BUTTON_POSITION_LEFT, 1)
->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
Menu
$menu
->addMenuItem($listItem)
->addMenuItem($gridItem)
->addMenuItem($tableItem);
MenuRegistry
$menuRegistry
->addMenu($viewMenu)
->addMenu($filterMenu);
These changes provide a more fluent API while maintaining backward
compatibility, as the return values were previously ignored (
void).
Design Rationale
Why fluent interface instead of named parameters?
The ComponentFactory intentionally uses a fluent interface approach (chained method calls) rather than accepting parameters in factory methods. This design decision was made for several important reasons:
- Consistency with TYPO3 patterns
- The fluent interface pattern is well-established throughout TYPO3's codebase and familiar to extension developers. Introducing a different pattern here would be inconsistent with the rest of the framework.
- Diverse button types with different properties
- Different button types have vastly different configuration requirements. For example, InputButton needs name/value/form attributes, LinkButton needs href attributes, DropDownButton needs items, and SplitButton needs primary and secondary actions. A unified parameter-based approach does not fit this diversity well and would lead to confusing method signatures with many optional parameters.
- Pre-configured buttons solve common cases
- The factory already provides pre-configured methods like
create,Save Button () create,Back Button () create,Close Button () create, andReload Button () createthat handle the most common use cases with minimal code.View Button () - Avoids duplication and maintenance burden
- A parameter-based approach would require duplicating all button-specific configuration knowledge in both the button classes and the factory methods. This creates a maintenance burden where changes to a button's properties must be reflected in multiple locations.
- Keeps factory simple and maintainable
- By keeping factory methods focused on instantiation, the ComponentFactory remains simple, maintainable, and easy to extend. Each button class maintains complete ownership of its own configuration logic.
Fluent interface is already concise
// Common case – use pre-configured button
$saveButton = $this->componentFactory->createSaveButton('myform');
// Custom case - fluent interface is clear and flexible
$customButton = $this->componentFactory->createInputButton()
->setName('custom_action')
->setValue('1')
->setTitle('Custom Action')
->setIcon(
$this->iconFactory->getIcon('actions-heart', IconSize::SMALL)
);
This design ensures the API remains maintainable, consistent with TYPO3 conventions and suitable for the diverse requirements of different button types while still providing convenience methods for common patterns.