Skip to content

[!!!][FEATURE] Register file renderers as tagged services #1808

Description

@TYPO3IncTeam

ℹ️ View this commit on Github
👥 Authored by Benni Mack benni@typo3.org
✔️ Merged by Stefan Bürk stefan@buerk.tech

Commit message

[!!!][FEATURE] Register file renderers as tagged services

File renderers are now registered as tagged services via the new
PHP attribute #[AsFileRenderer] (or the service tag
"fal.file_renderer") instead of the programmatic registration
through RendererRegistry->registerRendererClass() in
ext_localconf.php. The renderer priority is defined at
registration time via the attribute, renderers with a higher
priority are asked first whether they can render a given file.

The registration is resolved once at container compile time
instead of executing registration code on every request: loading
and validating the renderer classes per request as well as
instantiating and sorting them by priority at runtime is gone,
the compiled container provides a pre-sorted lazy iterator.
Renderers become proper services with constructor injection,
their order no longer depends on the ext_localconf.php loading
order, and the new FileRendererPass validates at compile time
that every tagged service implements FileRendererInterface,
failing the container build instead of erroring at render time.

This comes with the following breaking changes:

  • RendererRegistry->registerRendererClass() is now a no-op
    and will be removed in TYPO3 v16.0.
  • FileRendererInterface does not extend SingletonInterface
    anymore, renderers are shared services managed by the DI
    container.
  • getPriority() has been removed from FileRendererInterface,
    the remaining methods are now strictly typed.
  • RendererRegistry is marked @internal, its remaining public
    API is getRenderer(), getRendererInstances() is protected
    and the priority sorting is delegated to the container.

Resolves: #110277
Releases: main
Change-Id: I7b90fcedf1d81a671266dd402fbc28b680fba3ac
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/94910
Tested-by: core-ci typo3@b13.com
Reviewed-by: Oli Bartsch bo@cedev.de
Tested-by: Oli Bartsch bo@cedev.de
Tested-by: Stefan Bürk stefan@buerk.tech
Reviewed-by: Stefan Bürk stefan@buerk.tech

➕ Added files

15.0/Breaking-110277-FileRendererRegistrationAndInterfaceChanged.rst
..  include:: /Includes.rst.txt

..  _breaking-110277-1784812454:

======================================================================
Breaking: #110277 - File renderer registration and interface changed
======================================================================

See :issue:`110277`

Description
===========

File renderers, used for example by the :html:`<f:media>` ViewHelper to
render audio, video or online media files, are now registered as tagged
services via dependency injection (see
:ref:`feature-110277-1784812454`). This comes with the following breaking
changes:

-   :php:`\TYPO3\CMS\Core\Resource\Rendering\RendererRegistry->registerRendererClass()`
    is now a no-op. Calling the method has no effect anymore, but triggers
    an :php:`E_USER_DEPRECATED` notice. The method will be removed in
    TYPO3 v16.0.

-   :php:`\TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface` does
    not extend :php:`\TYPO3\CMS\Core\SingletonInterface` anymore. File
    renderers are shared services managed by the dependency injection
    container.

-   The method :php:`getPriority()` has been removed from
    :php:`FileRendererInterface`. The renderer priority is now defined at
    registration time via the :php:`#[AsFileRenderer]` attribute or the
    :yaml:`fal.file_renderer` service tag. Note that the file renderers
    shipped with TYPO3 Core previously returned a priority of :php:`1`
    from :php:`getPriority()` and are now registered with the attribute's
    default priority of :php:`0`. Custom renderers that relied on a
    priority of :php:`0` to rank strictly below all Core renderers now
    rank equally with them instead and should use a negative priority to
    keep the previous ordering.

-   Renderers registered with the same priority are no longer guaranteed
    to be asked in the order they were added: previously, same-priority
    renderers kept the order in which
    :php:`RendererRegistry->registerRendererClass()` was called from
    :file:`ext_localconf.php`. The order of same-priority tagged services
    is now an implementation detail of the dependency injection container
    and must not be relied upon. Extensions that depend on a specific
    evaluation order between renderers should assign distinct priorities
    instead.

-   The remaining methods of :php:`FileRendererInterface` are now
    strictly typed: :php:`canRender(FileInterface $file): bool` and
    :php:`render(FileInterface $file, int|string $width, int|string $height, array $options = []): string`.

-   The methods :php:`createRendererInstance()` and
    :php:`compareRendererPriority()` have been removed from
    :php:`RendererRegistry`, the method :php:`getRendererInstances()` has
    been changed from public to protected visibility, and the remaining
    methods are now strictly typed.

-   :php:`RendererRegistry` is now marked as :php:`@internal`, since
    registering file renderers does not require interacting with the
    registry anymore. TYPO3 Core resolves the matching renderer via
    :php:`getRenderer()` internally, for example in the
    :html:`<f:media>` ViewHelper.

Impact
======

File renderers registered via
:php:`RendererRegistry->registerRendererClass()` in
:file:`ext_localconf.php` are no longer evaluated. The corresponding
files (audio, video or online media) are no longer rendered by the custom
renderer until it is registered as a tagged service.

Custom renderer classes implementing :php:`FileRendererInterface` without
the adapted method signatures will cause a fatal PHP error.

Affected installations
======================

All installations with custom extensions registering file renderers via
:php:`RendererRegistry->registerRendererClass()`, or providing custom
implementations of :php:`FileRendererInterface`. The extension scanner
reports usages of :php:`registerRendererClass()` as weak match.

Migration
=========

Remove the :php:`RendererRegistry->registerRendererClass()` call from
:file:`ext_localconf.php` and add the :php:`#[AsFileRenderer]` attribute
to the renderer class instead. Move the priority previously returned by
:php:`getPriority()` to the attribute and remove the method. Add the
native type declarations to :php:`canRender()` and :php:`render()`:

..  code-block:: php
    :caption: EXT:my_extension/Classes/Resource/Rendering/MyVideoRenderer.php

    use TYPO3\CMS\Core\Attribute\AsFileRenderer;
    use TYPO3\CMS\Core\Resource\FileInterface;
    use TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface;

    #[AsFileRenderer(priority: 10)]
    final class MyVideoRenderer implements FileRendererInterface
    {
        public function canRender(FileInterface $file): bool
        {
            // ...
        }

        public function render(FileInterface $file, int|string $width, int|string $height, array $options = []): string
        {
            // ...
        }
    }

In case the extension supports both TYPO3 v14 and v15, keep the
:php:`getPriority()` method (it is simply unused in v15) and register the
renderer in both ways: the :file:`ext_localconf.php` registration is
evaluated in v14, the attribute in v15. Since PHP parameter types must
not be narrowed in implementations, keep the :php:`$width` and
:php:`$height` parameters untyped in this case — only the :php:`bool`
and :php:`string` return type declarations are compatible with both
versions.

Code that called :php:`RendererRegistry->getRendererInstances()` to
inspect all registered renderers should inject :php:`RendererRegistry`
and use :php:`getRenderer($file)` to retrieve the matching renderer for
a given file instead.

..  index:: FAL, PHP-API, PartiallyScanned, ext:core
15.0/Feature-110277-RegisterFileRenderersAsTaggedServices.rst
..  include:: /Includes.rst.txt

..  _feature-110277-1784812454:

=============================================================
Feature: #110277 - Register file renderers as tagged services
=============================================================

See :issue:`110277`

Description
===========

File renderers, used for example by the :html:`<f:media>` ViewHelper to
render audio, video or online media files, are now registered as tagged
services via dependency injection instead of the previous programmatic
registration through
:php:`\TYPO3\CMS\Core\Resource\Rendering\RendererRegistry->registerRendererClass()`
in :file:`ext_localconf.php`.

A file renderer class implementing
:php:`\TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface` is
registered by adding the new PHP attribute
:php:`\TYPO3\CMS\Core\Attribute\AsFileRenderer` to the class:

..  code-block:: php
    :caption: EXT:my_extension/Classes/Resource/Rendering/MyVideoRenderer.php

    use TYPO3\CMS\Core\Attribute\AsFileRenderer;
    use TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface;

    #[AsFileRenderer(priority: 10)]
    final class MyVideoRenderer implements FileRendererInterface
    {
        // ...
    }

The renderer priority is defined at registration time via the attribute.
Renderers with a higher priority are asked first whether they can render
a given file (:php:`canRender()`). The file renderers shipped with TYPO3
Core are registered with the default priority :php:`0` (previously
:php:`1` via the removed :php:`getPriority()` method), so any custom
renderer using a priority above :php:`0` takes precedence over them. The
order in which renderers of the same priority are evaluated is not
defined and must not be relied upon; use distinct priorities if the
evaluation order matters.

Alternatively, the service tag :yaml:`fal.file_renderer` can be used
directly in :file:`Configuration/Services.yaml`:

..  code-block:: yaml
    :caption: EXT:my_extension/Configuration/Services.yaml

    MyVendor\MyExtension\Resource\Rendering\MyVideoRenderer:
      tags:
        - name: fal.file_renderer
          priority: 10

Impact
======

Registering file renderers as tagged services has the following
benefits over the previous programmatic registration:

-   The registration is resolved once at container compile time instead
    of executing registration code from :file:`ext_localconf.php` on
    every request. Loading and validating the renderer classes per
    request, as well as instantiating and sorting them by priority at
    runtime, is not necessary anymore.

-   File renderers are proper services now and can use dependency
    injection in their constructor.

-   The renderer priority is declared at the class itself instead of
    depending on the loading order of :file:`ext_localconf.php` files.

-   The registration is validated at container compile time: a service
    tagged as :yaml:`fal.file_renderer` that does not implement
    :php:`FileRendererInterface` fails the container build with a
    speaking exception, instead of causing errors when a file is
    rendered.

Registration in :file:`ext_localconf.php` via
:php:`RendererRegistry->registerRendererClass()` is not evaluated
anymore, see :ref:`breaking-110277-1784812454` for the upgrade path.

..  index:: FAL, PHP-API, ext:core

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions