Skip to content

Commit c698eab

Browse files
bmackgeorgringer
authored andcommitted
[!!!][FEATURE] Add TypolinkBuilderInterface with automatic DI
Introduces a new TypolinkBuilderInterface to replace the previous AbstractTypolinkBuilder approach, enabling proper dependency injection for TypolinkBuilder implementations. Key changes: * New TypolinkBuilderInterface with buildLink() method * All core TypolinkBuilder implementations now use DI * Automatic DI configuration for all interface implementations * AbstractTypolinkBuilder constructor removed (breaking) * build() method deprecated in favor of buildLink() All implementations of TypolinkBuilderInterface are automatically configured as public services via DI container autoconfiguration, eliminating the need for manual service configuration. The new buildLink() method provides request context directly and supports proper dependency injection patterns. Breaking changes: * AbstractTypolinkBuilder constructor signature changed * Method signatures changed from build() to buildLink() * Custom implementations must implement the new interface Deprecations: * AbstractTypolinkBuilder->build() method * AbstractTypolinkBuilder->$contentObjectRenderer property Resolves: #106405 Releases: main Change-Id: I35183732a528e9527c5fec41ea472c68b496d5c2 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/86036 Reviewed-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Georg Ringer <georg.ringer@gmail.com> Reviewed-by: Georg Ringer <georg.ringer@gmail.com> Tested-by: core-ci <typo3@b13.com>
1 parent 0ec1c1a commit c698eab

24 files changed

+748
-328
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
.. include:: /Includes.rst.txt
2+
3+
.. _breaking-106405-1742674605:
4+
5+
=====================================================
6+
Breaking: #106405 - TypolinkBuilder signature changes
7+
=====================================================
8+
9+
See :issue:`106405`
10+
11+
Description
12+
===========
13+
14+
To enable dependency injection for TypolinkBuilder classes, several breaking
15+
changes were introduced to the TypolinkBuilder architecture.
16+
17+
The following breaking changes have been made:
18+
19+
* The constructor of :php:`TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder`
20+
has been removed. Extending classes can no longer rely on receiving
21+
:php:`ContentObjectRenderer` and :php:`TypoScriptFrontendController`
22+
through the constructor.
23+
24+
* All concrete TypolinkBuilder implementations now implement the new
25+
:php:`TYPO3\CMS\Frontend\Typolink\TypolinkBuilderInterface` and use
26+
dependency injection via their constructors instead of extending
27+
:php:`AbstractTypolinkBuilder` with constructor arguments.
28+
29+
* The method signature of the main link building method has changed from
30+
:php:`build(array &$linkDetails, string $linkText, string $target, array $conf)`
31+
to :php:`buildLink(array $linkDetails, array $configuration, ServerRequestInterface $request, string $linkText = '')`.
32+
33+
34+
Impact
35+
======
36+
37+
Custom TypolinkBuilder implementations extending
38+
:php:`AbstractTypolinkBuilder` will fail with fatal errors due to the
39+
removed constructor and changed method signatures.
40+
41+
Extensions that instantiate TypolinkBuilder classes directly will also
42+
fail, as the constructor signatures have fundamentally changed to use
43+
dependency injection.
44+
45+
46+
Affected installations
47+
======================
48+
49+
TYPO3 installations with extensions that:
50+
51+
* Create custom TypolinkBuilder classes extending :php:`AbstractTypolinkBuilder`
52+
* Directly instantiate TypolinkBuilder classes in PHP code
53+
* Override or extend the :php:`build()` method of TypolinkBuilder classes
54+
55+
56+
Migration
57+
=========
58+
59+
For custom TypolinkBuilder implementations:
60+
61+
1. Implement :php:`TYPO3\CMS\Frontend\Typolink\TypolinkBuilderInterface`
62+
2. Use dependency injection in the constructor for required services
63+
3. Replace the :php:`build()` method with :php:`buildLink()`
64+
65+
Note: Classes implementing :php:`TypolinkBuilderInterface` are automatically
66+
configured as public services in the DI container - no manual configuration
67+
is required.
68+
69+
Example migration:
70+
71+
.. code-block:: php
72+
:caption: Before (TYPO3 v13 and lower)
73+
74+
use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
75+
76+
class MyCustomLinkBuilder extends AbstractTypolinkBuilder
77+
{
78+
public function build(array &$linkDetails, string $linkText, string $target, array $conf): LinkResultInterface
79+
{
80+
// Custom link building logic
81+
return new LinkResult('news', $linkText);
82+
}
83+
}
84+
85+
.. code-block:: php
86+
:caption: After (TYPO3 v14+)
87+
88+
use Psr\Http\Message\ServerRequestInterface;
89+
use TYPO3\CMS\Frontend\Typolink\TypolinkBuilderInterface;
90+
91+
class MyCustomLinkBuilder implements TypolinkBuilderInterface
92+
{
93+
public function __construct(
94+
// Inject required dependencies
95+
) {}
96+
97+
public function buildLink(array $linkDetails, array $configuration, ServerRequestInterface $request, string $linkText = ''): LinkResultInterface
98+
{
99+
// Custom link building logic - access ContentObjectRenderer via:
100+
$contentObjectRenderer = $request->getAttribute('currentContentObject');
101+
return new LinkResult('news', $linkText);
102+
}
103+
}
104+
105+
For code that instantiates TypolinkBuilder classes directly:
106+
107+
It is strongly recommended to use the :php:`TYPO3\CMS\Frontend\Typolink\LinkFactory`
108+
instead of instantiating TypolinkBuilder classes directly. The LinkFactory
109+
handles the proper instantiation and dependency injection automatically.
110+
111+
.. index:: PHP-API, NotScanned, ext:frontend
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
.. include:: /Includes.rst.txt
2+
3+
.. _deprecation-106405-1742674605:
4+
5+
=====================================================
6+
Deprecation: #106405 - AbstractTypolinkBuilder->build
7+
=====================================================
8+
9+
See :issue:`106405`
10+
11+
Description
12+
===========
13+
14+
The :php:`build()` method in :php:`TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder`
15+
has been deprecated in favor of the new :php:`TypolinkBuilderInterface`.
16+
17+
When creating custom TypolinkBuilder classes, the traditional approach of:
18+
19+
1. Extending :php:`AbstractTypolinkBuilder`
20+
2. Implementing the :php:`build()` method
21+
3. Receiving dependencies via constructor
22+
23+
This approach is now deprecated. The new recommended approach is to
24+
implement the :php:`TypolinkBuilderInterface`.
25+
26+
The :php:`ContentObjectRenderer` property in :php:`AbstractTypolinkBuilder`
27+
is also deprecated and will be removed in TYPO3 v15.0. The
28+
:php:`ContentObjectRenderer` should be accessed via the
29+
:php:`ServerRequestInterface` object instead.
30+
31+
32+
Impact
33+
======
34+
35+
Extension authors who have created custom TypolinkBuilder classes
36+
extending from :php:`AbstractTypolinkBuilder` will see deprecation
37+
warnings when their link builders are used.
38+
39+
The deprecation warnings will be triggered when:
40+
41+
* Custom TypolinkBuilder classes still use the :php:`build()` method
42+
* Code relies on the :php:`$contentObjectRenderer` property
43+
* The old constructor approach is used for dependency handling
44+
45+
46+
Affected installations
47+
======================
48+
49+
TYPO3 installations with extensions that:
50+
51+
* Create custom TypolinkBuilder classes extending :php:`AbstractTypolinkBuilder`
52+
* Override or extend the :php:`build()` method
53+
* Access the :php:`$contentObjectRenderer` property directly
54+
55+
56+
Migration
57+
=========
58+
59+
For end users, generating links works the same way as before via:
60+
61+
* :php:`ContentObjectRenderer->typolink()` method
62+
* :php:`LinkFactory` class
63+
64+
No deprecation warnings will be triggered when using the public APIs.
65+
66+
For extension developers with custom TypolinkBuilder classes:
67+
68+
1. Implement the new interface:
69+
70+
.. code-block:: php
71+
:caption: Recommended migration approach
72+
73+
// Before (deprecated)
74+
class MyCustomLinkBuilder extends AbstractTypolinkBuilder
75+
{
76+
public function build(array &$linkDetails, string $linkText, string $target, array $conf): LinkResultInterface
77+
{
78+
// Custom logic using $this->contentObjectRenderer
79+
}
80+
}
81+
82+
// After (recommended)
83+
class MyCustomLinkBuilder implements TypolinkBuilderInterface
84+
{
85+
public function buildLink(array $linkDetails, array $configuration, ServerRequestInterface $request, string $linkText = ''): LinkResultInterface
86+
{
87+
$contentObjectRenderer = $request->getAttribute('currentContentObject');
88+
// Custom logic using $contentObjectRenderer
89+
}
90+
}
91+
92+
2. Use dependency injection for required services instead of accessing
93+
them via global state or constructor arguments.
94+
95+
3. Access ContentObjectRenderer via the request object rather than
96+
the deprecated property.
97+
98+
Note: All implementations of :php:`TypolinkBuilderInterface` are automatically
99+
configured as public services in the DI container - no manual service
100+
configuration is needed.
101+
102+
Extensions can maintain backward compatibility during the transition period
103+
by implementing both the old :php:`build()` method and the new
104+
:php:`buildLink()` method.
105+
106+
.. index:: PHP-API, NotScanned, ext:frontend
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
.. include:: /Includes.rst.txt
2+
3+
.. _feature-106405-1742674556:
4+
5+
===========================================
6+
Feature: #106405 - TypolinkBuilderInterface
7+
===========================================
8+
9+
See :issue:`106405`
10+
11+
Description
12+
===========
13+
14+
A new interface :php:`TYPO3\CMS\Frontend\Typolink\TypolinkBuilderInterface`
15+
has been introduced to provide a more flexible way to generate links in TYPO3.
16+
17+
The interface defines a :php:`buildLink()` method that replaces the previous
18+
:php:`build()` method approach when extending from
19+
:php:`TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder`.
20+
21+
All core TypolinkBuilder implementations now implement this interface and use
22+
dependency injection for better service composition and testability.
23+
24+
The interface method signature is:
25+
26+
.. code-block:: php
27+
28+
public function buildLink(
29+
array $linkDetails,
30+
array $configuration,
31+
ServerRequestInterface $request,
32+
string $linkText = ''
33+
): LinkResultInterface;
34+
35+
Impact
36+
======
37+
38+
* All implementations of :php:`TypolinkBuilderInterface` are automatically
39+
configured as public services in the DI container, eliminating the need for
40+
manual service configuration.
41+
42+
* TypolinkBuilder classes can now use proper dependency injection through
43+
their constructors, making them more testable and following TYPO3's
44+
architectural patterns.
45+
46+
* The :php:`ServerRequestInterface` is now passed directly,
47+
providing better access to request context without relying on global state.
48+
49+
* The new interface provides a cleaner separation of concerns
50+
and more explicit parameter passing.
51+
52+
53+
Example usage
54+
=============
55+
56+
Creating a custom TypolinkBuilder with the new interface:
57+
58+
.. code-block:: php
59+
:caption: Custom TypolinkBuilder implementation
60+
61+
use Psr\Http\Message\ServerRequestInterface;
62+
use TYPO3\CMS\Frontend\Typolink\TypolinkBuilderInterface;
63+
64+
class MyCustomLinkBuilder implements TypolinkBuilderInterface
65+
{
66+
public function __construct(
67+
private readonly MyCustomService $customService,
68+
private readonly AnotherService $anotherService,
69+
) {}
70+
71+
public function buildLink(
72+
array $linkDetails,
73+
array $configuration,
74+
ServerRequestInterface $request,
75+
string $linkText = ''
76+
): LinkResultInterface {
77+
// Access ContentObjectRenderer from request
78+
$contentObjectRenderer = $request->getAttribute('currentContentObject');
79+
80+
// Use injected services
81+
$processedData = $this->customService->process($linkDetails);
82+
83+
// Build and return link result
84+
return new LinkResult($processedData['url'], $linkText);
85+
}
86+
}
87+
88+
Registering the TypolinkBuilder class is still necessary via
89+
:php:`$GLOBALS['TYPO3_CONF_VARS']`.
90+
91+
.. index:: PHP-API, ext:frontend

typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -639,15 +639,15 @@ protected function prepareMenuItemsForDirectoryMenu($specialValue, $sortingField
639639
$specialValue = $this->request->getAttribute('frontend.page.information')->getId();
640640
}
641641
$items = GeneralUtility::intExplode(',', (string)$specialValue);
642-
$pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
642+
$pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class);
643643
foreach ($items as $id) {
644-
$MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($id);
644+
$MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($id, $this->parent_cObj->getRequest());
645645
// Checking if a page is a mount page and if so, change the ID and set the MP var properly.
646646
$mount_info = $this->sys_page->getMountPointInfo($id);
647647
if (is_array($mount_info)) {
648648
if ($mount_info['overlay']) {
649649
// Overlays should already have their full MPvars calculated:
650-
$MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$mount_info['mount_pid']);
650+
$MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps((int)$mount_info['mount_pid'], $this->parent_cObj->getRequest());
651651
$MP = $MP ?: $mount_info['MPvar'];
652652
} else {
653653
$MP = ($MP ? $MP . ',' : '') . $mount_info['MPvar'];
@@ -688,10 +688,10 @@ protected function prepareMenuItemsForListMenu($specialValue)
688688
array_flip(array_intersect(array_values($pageIds), array_keys($pageRecords))),
689689
$pageRecords
690690
);
691-
$pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class, $this->parent_cObj);
691+
$pageLinkBuilder = GeneralUtility::makeInstance(PageLinkBuilder::class);
692692
foreach ($pageRecords as $row) {
693693
$pageId = (int)$row['uid'];
694-
$MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($pageId);
694+
$MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($pageId, $this->parent_cObj->getRequest());
695695
// Keep mount point?
696696
$mount_info = $this->sys_page->getMountPointInfo($pageId, $row);
697697
// $pageId is a valid mount point
@@ -709,7 +709,7 @@ protected function prepareMenuItemsForListMenu($specialValue)
709709
$row['_MP_PARAM'] = $mount_info['MPvar'];
710710
// Overlays should already have their full MPvars calculated, that's why we unset the
711711
// existing $row['_MP_PARAM'], as the full $MP will be added again below
712-
$MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($mountedPageId);
712+
$MP = $pageLinkBuilder->getMountPointParameterFromRootPointMaps($mountedPageId, $this->parent_cObj->getRequest());
713713
if ($MP) {
714714
unset($row['_MP_PARAM']);
715715
}

0 commit comments

Comments
 (0)