Using Fluid in TYPO3 

Here are some examples of how Fluid can be used in TYPO3:

Changed in version 14.0

These classes were marked as deprecated in TYPO3 v13.3 and have been removed in v14:

  • \TYPO3\CMS\Fluid\View\StandaloneView
  • \TYPO3\CMS\Fluid\View\TemplateView
  • \TYPO3\CMS\Fluid\View\AbstractTemplateView
  • \TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface
  • \TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver

ViewHelper namespaces 

Defining global Fluid namespaces 

New in version 14.1

The extension-level configuration file Configuration/Fluid/Namespaces.php registers and extends global Fluid namespaces. Previously, the configuration $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces'] was used.

For example, we can define two global namespaces with the identifiers 'myext' and 'mycmp':

EXT:my_extension/Configuration/Fluid/Namespaces.php
<?php

return [
    'myext' => ['MyVendor\\MyExtension\\ViewHelpers'],
    'mycmp' => ['MyVendor\\MyExtension\\Components'],
];
Copied!

Assuming you have defined a Fluid component in EXT:my_extension/Resources/Private/Components/Button/Button.fluid.html then you can access the Button component via

EXT:my_extension/Resources/Private/Templates/SomeOtherTemplate.fluid.html
<mycmp:button title="{title}" teaser="{teaser}" />
Copied!

It is possible to override ViewHelpers that are in another extension. This is done by TYPO3 reading and merging Configuration/Fluid/Namespaces.php files in loaded extensions in the usual loading order. Loading order can be changed by declaring dependencies in composer.json (and possibly ext_emconf.php). In other words, if an extension registers a namespace that has already been registered by another extension, Fluid will merge the namespaces.

Example (my_extension2 depends on my_extension1):

EXT:my_extension1/Configuration/Fluid/Namespaces.php
<?php

return [
    'myext' => ['MyVendor\\MyExtension1\\ViewHelpers'],
];
Copied!
EXT:my_extension2/Configuration/Fluid/Namespaces.php
<?php

return [
    'myext' => ['MyVendor\\MyExtension2\\ViewHelpers'],
];
Copied!

This results in namespace definition:

[
    'myext' => [
        'MyVendor\\MyExtension1\\ViewHelpers',
        'MyVendor\\MyExtension2\\ViewHelpers',
    ],
];
Copied!

The processing order is in reverse, which means that <myext:demo /> would first check for EXT:my_extension2/Classes/ViewHelpers/DemoViewHelper.php, and then fall back to EXT:my_extension1/Classes/ViewHelpers/DemoViewHelper.php.

Importing Fluid namespaces locally 

Say you have defined a Fluid component in EXT:my_extension/Resources/Private/Components/Button/Button.fluid.html. Instead of defining the Fluid namespace globally you can specify the Fluid namespace like this:

EXT:my_extension/Resources/Private/Templates/SomeOtherTemplate.fluid.html
<html
    xmlns:my="http://typo3.org/ns/MyVendor/MyExtension/Components"
    data-namespace-typo3-fluid="true"
>

<my:button title="{title}" teaser="{teaser}"/>
Copied!

The namespace here is 'my'. For further information visit ViewHelper namespaces in Fluid explained.

Using Fluid components 

Description 

With version 4.3 the concept of components was introduced into Fluid.

Introduction to Fluid components 

The typical look of a component is like a normal Fluid template, except that it defines all of its arguments with the Argument ViewHelper <f:argument>. The Slot ViewHelper <f:slot> can be used to receive other HTML content. With the Slot ViewHelper it is possible to nest components.

Here is an example of how you could define a Fluid component:

EXT:my_extension/Resources/Private/Components/Molecule/TeaserCard/TeaserCard.fluid.html
<html
    xmlns:my="http://typo3.org/ns/MyVendor/MyExtension/Components"
    data-namespace-typo3-fluid="true"
>

<f:argument name="title" type="string" />
<f:argument name="link" type="string" />
<f:argument name="icon" type="string" optional="{true}" />

<a href="{link}" class="teaserCard">
    <f:if condition="{icon}">
        <my:atom.icon identifier="{icon}">
    </f:if>
    <div class="teaserCard__title">{title}</div>
    <div class="teaserCard__content"><f:slot /></div>
</a>
Copied!

The example also demonstrates that components can (and should) use other components, in this case <my:atom.icon>. Depending on the use case, it might also make sense to pass the output of one component to another component via a slot:

<html
    xmlns:my="http://typo3.org/ns/MyVendor/MyExtension/Components"
    data-namespace-typo3-fluid="true"
>

<my:molecule.teaserCard
    title="TYPO3"
    link="https://typo3.org/"
    icon="typo3"
>
    <my:atom.text>{content}</my:atom.text>
</my:molecule.teaserCard>
Copied!

You can learn more about components in Defining Components. Note that this is part of the Fluid Standalone documentation, which means that it doesn't mention TYPO3 specifics.

Registering component collections 

In order to use Fluid components, register a component collection within the file Configuration/Fluid/ComponentCollections.php

EXT:my_extension/Configuration/Fluid/ComponentCollections.php
<?php

return [
    'MyVendor\\MyExtension\\Components' => [
        'templatePaths' => [
            10 => 'EXT:my_extension/Resources/Private/Components',
        ],
    ],
];
Copied!

in which you define the path where your Fluid components can be found. Components in these collections can then be used in any Fluid template.

EXT:my_extension/Resources/Private/Templates/Template.fluid.html
<html
    xmlns:my="http://typo3.org/ns/MyVendor/MyExtension/Components"
    data-namespace-typo3-fluid="true"
>

<my:organism.header.navigation />
Copied!

Note that, by default, component collections use a folder structure that requires a separate directory for each component. That means, for example, if you have defined a <my:organism.header.navigation /> Fluid component then the template file should be stored in EXT:my_extension/Resources/Private/Components/Organism/Header/Navigation/Navigation.fluid.html.

All arguments that are passed to a component need to be defined with <f:argument> in the component template, for example Navigation.fluid.html.

It is possible to adjust these configurations per collection:

  • using templateNamePattern allows you to use a different folder structure, available variables are {path} and {name}. For example, with <my:organism.header.navigation>, {path} would be Organism/Header and {name} would be Navigation.
  • setting additionalArgumentsAllowed to true allows passing undefined arguments to components.

Here is an example where these configurations are used.

EXT:my_extension/Configuration/Fluid/ComponentCollections.php
<?php

return [
    'MyVendor\\MyExtension\\Components' => [
        'templatePaths' => [
            10 => 'EXT:my_extension/Resources/Private/Components',
        ],
        'templateNamePattern' => '{path}/{name}',
        'additionalArgumentsAllowed' => true,
    ],
];
Copied!

Using this example, <my:organism.header.navigation /> would point to EXT:my_extension/Resources/Private/Components/Organism/Header/Navigation.fluid.html (note the missing Navigation folder).

It is possible to influence certain aspects of Fluid components using PSR-14 events, see PSR-14 events for Fluid components.

History of Fluid components 

In TYPO3 v13 it is possible to use components in TYPO3 projects by creating a custom ComponentCollection class that essentially connects a folder of template files to a Fluid ViewHelper namespace. Using that class it is also possible to use an alternative folder structure for a component collection and to allow arbitrary arguments to be passed to components in that collection.

Migration and co-existence with class-based collections 

Since TYPO3 v14 you should use the configuration-based component collections over the class-based. A configuration-based component collection is a collection defined by the configuration file ComponentCollections.php. In contrast to that, a class-based component required custom PHP code in TYPO3 v13, see Fluid components in Fluid explained. Most use cases can easily be migrated to the configuration-based approach, since they usually just consist of boilerplate code around the configuration options.

In fact, you can use both component collection types side by side. For more advanced use cases, it might still be best to ship a custom class to define a component collection. Since the configuration-based approach is not available in TYPO3 v13, it is possible to ship both variants to provide backwards-compatibility: if a specific component collection is defined both by class and by configuration, in TYPO3 v13 the class will be used, while in TYPO3 v14 the configuration will be used and the class will be ignored completely.

Extending component collections from other extensions 

It is possible to extend the configuration of other extensions using the introduced configuration file. This allows integrators to merge their own set of components into an existing component collection:

EXT:vendor_extension/Configuration/Fluid/ComponentCollections.php
<?php

return [
    'SomeVendor\\VendorExtension\\Components' => [
        'templatePaths' => [
            10 => 'EXT:vendor_extension/Resources/Private/Components',
        ],
    ],
];
Copied!
EXT:my_extension/Configuration/Fluid/ComponentCollections.php
<?php

return [
    'SomeVendor\\VendorExtension\\Components' => [
        'templatePaths' => [
            1765990741 => 'EXT:my_extension/Resources/Private/Extensions/VendorExtension/Components',
        ],
    ],
];
Copied!

For template paths, the familiar rule applies: they will be sorted by their keys and will be processed in reverse order. In this example, if my_extension defines a component that already exists in vendor_extension, it will override the original component in vendor_extension.

Using the generic view factory (ViewFactoryInterface) 

New in version 13.3

Class \TYPO3\CMS\Core\View\ViewFactoryInterface has been added as a generic view factory interface to create views that return an instance of \TYPO3\CMS\Core\View\ViewInterface . This implements the "V" of "MVC" in a generic way and is used throughout the TYPO3 core.

You can inject an instance of the \TYPO3\CMS\Core\View\ViewFactoryInterface to create an instance of a \TYPO3\CMS\Core\View\ViewInterface where you need one.

EXT:my_extension/Classes/Controller/MyController.php (Not Extbase)
<?php

namespace MyVendor\MyExtension\Controller;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\View\ViewFactoryData;
use TYPO3\CMS\Core\View\ViewFactoryInterface;

final readonly class MyController
{
    public function __construct(
        private ViewFactoryInterface $viewFactory,
    ) {}

    public function myAction(ServerRequestInterface $request): string
    {
        $viewFactoryData = new ViewFactoryData(
            templateRootPaths: ['EXT:my_extension/Resources/Private/Templates'],
            partialRootPaths: ['EXT:my_extension/Resources/Private/Partials'],
            layoutRootPaths: ['EXT:my_extension/Resources/Private/Layouts'],
            request: $request,
        );
        $view = $this->viewFactory->create($viewFactoryData);
        $view->assign('mykey', 'myValue');
        return $view->render('path/to/template');
    }
}
Copied!

The ViewFactoryInterface needs an instance of \TYPO3\CMS\Core\View\ViewFactoryData , which is a data object and should therefore be created via new.

Best practices in creating a ViewFactoryData instance:

  • Hand over request of type \Psr\Http\Message\ServerRequestInterface if possible. See Getting the PSR-7 request object.
  • Use the tuple $templateRootPaths, $partialRootPaths and $layoutRootPaths if possible by providing an array of "base" paths like 'EXT:my_extension/Resources/Private/(Templates|Partials|Layouts)'
  • Avoid using parameter $templatePathAndFilename
  • Call render('path/within/templateRootPath') without file-ending on the returned ViewInterface instance.