Using Fluid in TYPO3
Here are some examples of how Fluid can be used in TYPO3:
- Create a template (theme) using a combination of TypoScript FLUIDTEMPLATE and Fluid. Check out the TYPO3 site package tutorial which walks you through the creation of a sitepackage extension.
- Create a custom content element type in addition to the already existing content elements TYPO3 supplies.
- Extbase-based controllers have a default Fluid
view in
$this->view. - Use Fluid to create emails using the TYPO3 Mail API.
- Use Fluid in backend modules, either with or without Extbase.
- Use the generic view factory to create a Fluid view.
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\ Standalone View \TYPO3\CMS\ Fluid\ View\ Template View \TYPO3\CMS\ Fluid\ View\ Abstract Template View \TYPO3\CMS\ Extbase\ Mvc\ View\ View Resolver Interface \TYPO3\CMS\ Extbase\ Mvc\ View\ Generic View Resolver
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
was used.
For example, we can define two global namespaces with the identifiers 'myext' and 'mycmp':
<?php
return [
'myext' => ['MyVendor\\MyExtension\\ViewHelpers'],
'mycmp' => ['MyVendor\\MyExtension\\Components'],
];
Assuming you have defined a Fluid component in
EXT:
then you can access the Button component via
<mycmp:button title="{title}" teaser="{teaser}" />
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.
(and possibly ext_). 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):
<?php
return [
'myext' => ['MyVendor\\MyExtension1\\ViewHelpers'],
];
<?php
return [
'myext' => ['MyVendor\\MyExtension2\\ViewHelpers'],
];
This results in namespace definition:
[
'myext' => [
'MyVendor\\MyExtension1\\ViewHelpers',
'MyVendor\\MyExtension2\\ViewHelpers',
],
];
The processing order is in reverse, which means that
<myext: would first check for
EXT:, and then
fall back to EXT:.
Importing Fluid namespaces locally
Say you have defined a Fluid component in
EXT:.
Instead of defining the Fluid namespace globally you can specify
the Fluid namespace like this:
<html
xmlns:my="http://typo3.org/ns/MyVendor/MyExtension/Components"
data-namespace-typo3-fluid="true"
>
<my:button title="{title}" teaser="{teaser}"/>
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:
<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>
The example also demonstrates that components can (and should) use other components, in this
case
<my:.
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>
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
<?php
return [
'MyVendor\\MyExtension\\Components' => [
'templatePaths' => [
10 => 'EXT:my_extension/Resources/Private/Components',
],
],
];
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.
<html
xmlns:my="http://typo3.org/ns/MyVendor/MyExtension/Components"
data-namespace-typo3-fluid="true"
>
<my:organism.header.navigation />
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:
Fluid component then the template file
should be stored in EXT:.
All arguments that are passed to a component need to be defined with
<f: in the component template, for example
Navigation..
It is possible to adjust these configurations per collection:
- using
templateallows you to use a different folder structure, available variables areName Pattern {path}and{name}. For example, with<my:,organism. header. navigation> {path}would beOrganism/andHeader {name}would beNavigation. - setting
additionaltoArguments Allowed trueallows passing undefined arguments to components.
Here is an example where these configurations are used.
<?php
return [
'MyVendor\\MyExtension\\Components' => [
'templatePaths' => [
10 => 'EXT:my_extension/Resources/Private/Components',
],
'templateNamePattern' => '{path}/{name}',
'additionalArgumentsAllowed' => true,
],
];
Using this example,
<my: would point to
EXT:
(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
Component 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 Component. 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:
<?php
return [
'SomeVendor\\VendorExtension\\Components' => [
'templatePaths' => [
10 => 'EXT:vendor_extension/Resources/Private/Components',
],
],
];
<?php
return [
'SomeVendor\\VendorExtension\\Components' => [
'templatePaths' => [
1765990741 => 'EXT:my_extension/Resources/Private/Extensions/VendorExtension/Components',
],
],
];
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_
defines a component that already exists in vendor_, it will override
the original component in vendor_.
Using the generic view factory (ViewFactoryInterface)
New in version 13.3
Class
\TYPO3\ has been added as a
generic view factory interface to create views that return an instance of
\TYPO3\. 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\ to create an instance of a
\TYPO3\ where you need one.
Note
Extbase-based controllers create a view
instance based on this factory by default and which is accessible as
$this->view.
<?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');
}
}
The
View needs an instance of
\TYPO3\, which is a data object and should
therefore be created via
new.
Best practices in creating a
View
instance:
- Hand over request of type
\Psr\if possible. See Getting the PSR-7 request object.Http\ Message\ Server Request Interface - Use the tuple
$template,Root Paths $partialandRoot Paths $layoutif possible by providing an array of "base" paths likeRoot Paths 'EXT:my_ extension/ Resources/ Private/ (Templates |Partials |Layouts)' - Avoid using parameter
$templatePath And Filename - Call
renderwithout file-ending on the returned ViewInterface instance.('path/ within/ template Root Path')