TYPO3 v15 dev-main snapshot ()
This commit is contained in:
+370
@@ -0,0 +1,370 @@
|
||||
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _feature-69863:
|
||||
|
||||
=================================================================
|
||||
Feature: #69863 - Use new standalone Fluid as composer dependency
|
||||
=================================================================
|
||||
|
||||
See :issue:`69863`
|
||||
|
||||
Description
|
||||
===========
|
||||
|
||||
The Fluid rendering engine of TYPO3 CMS is replaced by the standalone capable Fluid which is now included as composer dependency.
|
||||
The old Fluid extension is converted to a so-called Fluid adapter which allows TYPO3 CMS to use standalone Fluid with the many
|
||||
new features this facilitates.
|
||||
|
||||
|
||||
Impact
|
||||
======
|
||||
|
||||
New features/capabilities have been added in nearly all areas of Fluid. Most importantly: several of the Fluid components which
|
||||
in the past were completely internal and impossible to replace, are now easy to replace and have been fitted with a public API.
|
||||
Unless noted otherwise each feature below is part of such a new API component which you can replace. This gives you unprecedented
|
||||
control over almost all of Fluid's behaviours in a way that can be controlled via each View instance, for example from within a
|
||||
controller's initializeView method.
|
||||
|
||||
Developers working with just the template side of Fluid will not notice many differences. Those that they will notice are, for
|
||||
the most part, hidden away beneath a compatibility flag that is set via the View, allowing each extension to opt-in to the
|
||||
extended capabilities or to stick with the legacy behaviour of Fluid. The new features that relate to these new behaviours are:
|
||||
|
||||
RenderingContext
|
||||
----------------
|
||||
|
||||
The most important new piece of public API is the RenderingContext. The previously internal-only RenderingContext used by Fluid
|
||||
has been expanded to be responsible for a vital new Fluid feature: implementation provisioning. This enables a developer to
|
||||
change a range of classes Fluid uses for parsing, resolving, caching etc. by either including a custom RenderingContext or
|
||||
manipulating the default RenderingContext by public methods, a developer is able to change a range of classes Fluid uses for
|
||||
parsing, resolving, caching etc.
|
||||
|
||||
Each component which can be replaced this way is described in further detail below.
|
||||
|
||||
Impact for template developers
|
||||
==============================
|
||||
|
||||
The following behaviours can all be controlled by manipulating the RenderingContext. By default, none of them are enabled - but
|
||||
calling a simple method (via your View instance) allows you to enable them:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$view->getRenderingContext()->setLegacyMode(false);
|
||||
|
||||
Doing so causes the RenderingContext to deliver different implementations, which simply means that in addition to what you
|
||||
already know Fluid to be capable of (variable access, inline syntax etc.) you gain access to the following features:
|
||||
|
||||
ExpressionNodes
|
||||
---------------
|
||||
|
||||
Expression Nodes are a new type of Fluid syntax structures which all share a common trait: they only work inside the curly braces
|
||||
previously only used by variable accessing and inline syntax of ViewHelpers. You can define the exact collection of
|
||||
ExpressionNodes that are active for your rendering process, via the View instance:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$view->getRenderingContext()->setExpressionNodeTypes(array(
|
||||
'Class\Number\One',
|
||||
'Class\Number\Two'
|
||||
));
|
||||
|
||||
When added to this collection these Expression Node types allow new syntaxes such as `{myVariable + 1}` or
|
||||
`{myArrayLikeObject as array}`. When the legacy mode toggle is set to `false` this will enable the following
|
||||
expression types:
|
||||
|
||||
1. CastingExpressionNode - this type allows casting a variable to certain types, for example to guarantee an integer or a
|
||||
boolean. It is used simply with an `as` keyword: `{myStringVariable as boolean}`, `{myBooleanVariable as integer}` and
|
||||
so on. Attempting to cast a variable to an incompatible type causes a standard Fluid error.
|
||||
2. MathExpressionNode - this type allows basic mathematical operations on variables, for example `{myNumber + 1}`,
|
||||
`{myPercent / 100}`, `{myNumber * 100}` and so on. An impossible expression returns an empty output.
|
||||
3. TernaryExpressionNode - this type allows an inline ternary condition which only operates on variables. The use case is "if
|
||||
this variable then use that variable else use another variable". It is used as
|
||||
`{myToggleVariable ? myThenVariable : myElseVariable}`. Note that it does not support any nested expressions, inline
|
||||
ViewHelper syntaxes or similar inside it - it must be used only with standard variables as input.
|
||||
|
||||
Developers can add their own additional ExpressionNodeTypes. Each one consists of a pattern to be matched and methods dictated
|
||||
by an interface to process the matches - any existing ExpressionNode type can be used as reference.
|
||||
|
||||
Namespaces are extensible
|
||||
-------------------------
|
||||
|
||||
Fluid now allows each namespace alias (for example `f:`) to be extended by adding to it additional PHP namespaces that are
|
||||
also checked for the presence of ViewHelper classes. This is what allows TYPO3 CMS to transparently add just the ViewHelpers that
|
||||
are unique to TYPO3 CMS and let Fluid add the rest. It also means that developers can override individual ViewHelpers with custom
|
||||
versions and have their ViewHelpers called when the `f:` namespace is used.
|
||||
|
||||
This change also implies that namespaces are no longer monadic - any time you use `{namespace f=My\Extension\ViewHelpers}` you
|
||||
will no longer receive an error with "namespace already registered". Fluid will instead add this PHP namespace and look for
|
||||
ViewHelpers there as well. Additional namespaces are checked from the bottom up, allowing the additional namespaces to override
|
||||
ViewHelper classes by placing them in the same scope (e.g. `f:format.nl2br` can be overridden with
|
||||
`My\Extension\ViewHelpers\Format\Nl2brViewHelper` given the namespace registration example above.
|
||||
|
||||
The behaviour is used both for legacy namespace registration in curly braces and the modern `xmlns` approach using a
|
||||
container HTML tag.
|
||||
|
||||
Rendering using f:render
|
||||
------------------------
|
||||
|
||||
This specific ViewHelper is fundamentally different in the standalone Fluid version - in the default (current) usage scenarios
|
||||
it behaves completely like you are used to, but has been fitted with some major impact features that you can use whenever you
|
||||
want to, in any template.
|
||||
|
||||
There are two specific changes both documented in their respective commits:
|
||||
|
||||
1. Default content (when section/partial is missing) now possible - https://github.com/TYPO3Fluid/Fluid/commit/cd67f9d974bc489058bde1c4272b480eb349da09
|
||||
2. Tag content of `f:render` can now be passed as a variable to the section/partial being rendered (essentially becoming a
|
||||
wrapping/block strategy) - https://github.com/TYPO3Fluid/Fluid/commit/454121cba81baed4e3fe526412ff3e14f7c499a9
|
||||
|
||||
All TagBasedViewHelpers natively support data- prefixed attributes
|
||||
------------------------------------------------------------------
|
||||
|
||||
Simply put - any TagBasedViewHelper can now receive `data-` prefixed attributes without requiring those attributes to be
|
||||
declared by the ViewHelper. Any suffix can be used as long as the prefix is `data-`.
|
||||
|
||||
Complex conditional statements
|
||||
------------------------------
|
||||
|
||||
As a forced new feature - which is backwards compatible - Fluid now supports any degree of complex conditional statements with
|
||||
nesting and grouping:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<f:if condition="({variableOne} && {variableTwo}) || {variableThree} || {variableFour}">
|
||||
// Done if both variable one and two evaluate to true, or if either variable three or four do.
|
||||
</f:if>
|
||||
|
||||
In addition, `f:else` has been fitted with an "elseif"-like behavior:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<f:if condition="{variableOne}">
|
||||
<f:then>Do this</f:then>
|
||||
<f:else if="{variableTwo}">Do this instead if variable two evals true</f:else>
|
||||
<f:else if="{variableThree}">Or do this if variable three evals true</f:else>
|
||||
<f:else>Or do this if nothing above is true</f:else>
|
||||
</f:if>
|
||||
|
||||
Dynamic variable name parts
|
||||
---------------------------
|
||||
|
||||
Another forced new feature, likewise backwards compatible, is the added ability to use sub-variable references when accessing
|
||||
your variables. Consider the following Fluid template variables array:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$mykey = 'foo'; // or 'bar', set by any source
|
||||
$view->assign('data', ['foo' => 1, 'bar' => 2]);
|
||||
$view->assign('key', $mykey);
|
||||
|
||||
With the following Fluid template:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
You chose: {data.{key}}.
|
||||
(output: "1" if key is "foo" or "2" if key is "bar")
|
||||
|
||||
The same approach can also be used to generate dynamic parts of a string variable name:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$mydynamicpart = 'First'; // or 'Second', set by any source
|
||||
$view->assign('myFirstVariable', 1);
|
||||
$view->assign('mySecondVariable', 2);
|
||||
$view->assign('which', $mydynamicpart);
|
||||
|
||||
With the following Fluid template:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
You chose: {my{which}Variable}.
|
||||
(output: "1" if which is "First" or "2" if which is "Second")
|
||||
|
||||
This syntax can be used anywhere a variable is referenced, with one exception: variables passed as pure variable accessors cannot
|
||||
contain dynamic parts, e.g. the following will **NOT** work:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
{f:if(condition: my{which}Variable, then: 'this', else: 'that')}
|
||||
|
||||
Whereas the following **will** work because the variables are accessed wrapped in a text node:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
{f:if(condition: '{my{which}Variable}', then: 'this', else: 'that')}
|
||||
|
||||
In other words: unless your outer variable reference is enclosed with curly braces, Fluid does not detect that you are
|
||||
referencing a dynamic variable and will instead assume you meant a variable actually named `my{which}Variable` which was added
|
||||
as `$view->assign('my{which}Variable', 'value')`.
|
||||
|
||||
New ViewHelpers
|
||||
---------------
|
||||
|
||||
A few new ViewHelpers have been added to the collection as part of standalone Fluid and as such are also available in TYPO3 from now on:
|
||||
|
||||
* `f:or` which is a shorter way to write (chained) conditions. It supports syntax like
|
||||
`{variableOne -> f:or(alternative: variableTwo) -> f:or(alternative: variableThree)}` which checks each variable and outputs
|
||||
the first one that's not empty.
|
||||
* `f:spaceless` which can be used in tag-mode around template code to eliminate redundant whitespace and blank lines for
|
||||
example caused by indenting ViewHelper usages.
|
||||
|
||||
Improved error reporting
|
||||
------------------------
|
||||
|
||||
Syntax errors or problems with required arguments or incorrect argument types will now be reported with line number and template
|
||||
code example from the line that fails. Any ViewHelper Exception is turned into this improved error type by converting it to a
|
||||
special syntax error and attaching the original Exception to it.
|
||||
|
||||
An example error could be:
|
||||
|
||||
``TYPO3Fluid\Fluid\Core\Parser\Exception: Fluid parse error in template Default_action_Default_1cb8dc11e29962882f629f79c0b9113ff33d6219,
|
||||
line 11 at character 3. Error: The ViewHelper "<f:serender>" could not be resolved. Based on your spelling, the system would load
|
||||
the class "TYPO3Fluid\Fluid\ViewHelpers\SerenderViewHelper", however this class does not exist. (1407060572). Template code:
|
||||
<f:serender section="Foo" optional="1">``. A stack trace is still included if TYPO3 does not run in Production context.
|
||||
|
||||
Impact for extension developers
|
||||
===============================
|
||||
|
||||
Extension developers are affected mainly by gaining access to a range of new APIs that control Fluid's behavior. These new APIs
|
||||
can all be accessed via the RenderingContext which is available in Views and ViewHelpers (also when compiled). Developers can
|
||||
provide custom implementations or manipulate the standard implementations by retrieving each API through the RenderingContext
|
||||
and using methods of those.
|
||||
|
||||
There are no significant changes to best practices and the ViewHelper API (which you use when creating custom ViewHelpers)
|
||||
remains largely untouched. The most notable change is that `$this->renderingContext` in ViewHelpers and Views now allows direct
|
||||
access to on-the-fly changes in Fluid's behavior.
|
||||
|
||||
RenderingContext as implementation API
|
||||
--------------------------------------
|
||||
|
||||
Rather than just being a simple context which hangs on to variables, the RenderingContext has been given a completely new and
|
||||
even more vital role in Fluid - it is now the API for delivering custom implementations for a range of features that until now
|
||||
were only possible to achieve via means like XCLASSing. A RenderingContext now delivers the following components:
|
||||
|
||||
* The VariableProvider (previously known as TemplateVariableContainer, see below) used in rendering
|
||||
* The ViewHelperVariableContainer (already known) used in rendering
|
||||
* The ViewHelperResolver (new pattern) responsible for handling namespaces and resolving/creating ViewHelper instances
|
||||
and arguments
|
||||
* The ViewHelperInvoker (new pattern) responsible for calling ViewHelpers (circumvented when ViewHelpers implement a custom
|
||||
`compile()` method)
|
||||
* The TemplatePaths (new pattern) which is a template file resolving class that now contains resolving methods previously found
|
||||
on the View itself
|
||||
* The TemplateParser (already known) which is responsible for parsing the template and creating a ParsedTemplate
|
||||
* The TemplateCompiler (already known) which is responsible for converting a ParsedTemplate to a native PHP class
|
||||
* The FluidCache (new pattern) which is a custom caching implementation compatible with TYPO3 caching frontends/backends
|
||||
storing PHP files
|
||||
* An array of ExpressionNodeTypes (class names, new pattern) - see description of those above
|
||||
* An array of TemplateProcessors (instances, new pattern) which pre-process template source code before it gets handed off to the
|
||||
TemplateParser, allowing things like extracting registered namespaces in custom ways.
|
||||
* The controller name, if one applies to the context
|
||||
* The controller action name, if one applies to the context
|
||||
* And for TYPO3 CMS only, the Extbase ControllerContext (which is as it has always been; contains a Request etc.).
|
||||
|
||||
All (!) of which can be replaced with custom implementations and all of which are accessible through View and ViewHelpers alike.
|
||||
Just a few of the capabilities you gain:
|
||||
|
||||
* You can create custom VariableProvider implementations which retrieve variables in new ways from new sources - Fluid itself now
|
||||
includes a JSON-based VariableProvider as well as a ChainedVariableProvider which allows "plugging" multiple variable sources.
|
||||
* You can create a custom ViewHelperResolver implementation which can do things like automatically register namespaces that are
|
||||
always available or change the way ViewHelper classes are detected, instantiated, how arguments are detected, and more.
|
||||
* You can create a custom ViewHelperInvoker implementation which calls ViewHelpers in new ways - combined with a custom
|
||||
ViewHelperResolver this can for example allow non-ViewHelper classes to be used as if they actually were ViewHelpers.
|
||||
* You can create custom TemplatePaths implementations which for example read template sources not from the local file system but
|
||||
from database, remote storage, zip files, whatever you desire.
|
||||
* You can replace the TemplateParser itself (but be careful if you do, obviously). There are no current use cases for this, but
|
||||
the possibility exists.
|
||||
* You can replace the TemplateCompiler (be careful here too). No use case exists but this could be used to compile Fluid
|
||||
templates to other things than PHP.
|
||||
* You can replace the Cache implementation - for example to cache compiled Fluid templates in memcache or a distributed cache
|
||||
accessible by PHP opcache.
|
||||
* You can change which Expression Node types are possible to use in templates rendered with your context, for example disabling
|
||||
ternary expressions or adding a custom type of expression of your own.
|
||||
* You can change which TemplateProcessors will be used to process templates when rendered with your context, to do whatever you
|
||||
like - transform, analyze and so on the template source.
|
||||
|
||||
All of these parts are possible to replace via the provided RenderingContext - you don't necessarily have to create your own -
|
||||
but when creating multiple implementations it is often easier to combine those in a custom RenderingContext and just provide
|
||||
that for your View.
|
||||
|
||||
But perhaps most importantly, because all of these components are contained in the RenderingContext which is available to Views
|
||||
and ViewHelpers alike (also once compiled!), it becomes possible for your View or ViewHelpers to actually interact with the Fluid
|
||||
environment in powerful ways. To illustrate how powerful, you could create a single ViewHelper which: manipulates the Expression
|
||||
Node types usable in its tag content, changes the paths used to resolve Partials, registers a number of other ViewHelper
|
||||
namespaces, changes the variable source to be a JSON file or URL and adds a pre-processing class that triggers on every template
|
||||
source read from within the ViewHelper's tag contents, to strip some undesired namespace from third party Partials. And it could
|
||||
restore the context afterwards so that all of this only applies inside that ViewHelper's tag content.
|
||||
|
||||
ViewHelper namespaces can be extended also from PHP
|
||||
---------------------------------------------------
|
||||
|
||||
By accessing the ViewHelperResolver of the RenderingContext, developers can change the ViewHelper namespace inclusions on a
|
||||
global (read: per View instance) basis:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$resolver = $view->getRenderingContext()->getViewHelperResolver();
|
||||
// equivalent of registering namespace in template(s):
|
||||
$resolver->registerNamespace('news', 'GeorgRinger\News\ViewHelpers');
|
||||
// adding additional PHP namespaces to check when resolving ViewHelpers:
|
||||
$resolver->extendNamespace('f', 'My\Extension\ViewHelpers');
|
||||
// setting all namespaces in advance, globally, before template parsing:
|
||||
$resolver->setNamespaces(array(
|
||||
'f' => array(
|
||||
'TYPO3Fluid\\Fluid\\ViewHelpers',
|
||||
'TYPO3\\CMS\\Fluid\\ViewHelpers',
|
||||
'My\\Extension\\ViewHelpers'
|
||||
),
|
||||
'vhs' => array(
|
||||
'FluidTYPO3\\Vhs\\ViewHelpers',
|
||||
'My\\Extension\\ViewHelpers'
|
||||
),
|
||||
'news' => array(
|
||||
'GeorgRinger\\News\\ViewHelpers',
|
||||
);
|
||||
));
|
||||
|
||||
By "extending" a namespace Fluid adds additional lookup namespaces when detecting ViewHelper classes and uses the last added path first, allowing you to replace ViewHelpers by placing a class with the same sub-name in your own ViewHelpers namespace that extends Fluid's. Doing so also allows you to change the arguments the ViewHelper accepts/requires.
|
||||
|
||||
ViewHelpers can accept arbitrary arguments
|
||||
------------------------------------------
|
||||
|
||||
This feature allows your ViewHelper class to receive any number of additional arguments using any names you desire. It works by
|
||||
separating the arguments that are passed to each ViewHelper into two groups: those that are declared using `registerArgument`
|
||||
(or render method arguments), and those that are not. Those that are not declared are then passed to a special function -
|
||||
`handleAdditionalArguments` - on the ViewHelper class, which in the default implementation throws an error if additional
|
||||
arguments exist. So by overriding this method in your ViewHelper you can change if and when the ViewHelper should throw an
|
||||
error on receiving unregistered arguments.
|
||||
|
||||
This feature is also the one allowing TagBasedViewHelpers to freely accept arbitrary `data-` prefixed arguments without
|
||||
failing - on TagBased ViewHelpers, the `handleAdditionalArguments` method simply adds new attributes to the tag that gets
|
||||
generated and throws an error if any additional arguments which are neither registered nor prefixed with `data-` are given.
|
||||
|
||||
ViewHelpers automatically compilable
|
||||
------------------------------------
|
||||
|
||||
All ViewHelpers, including those you write yourself, are now automatically compilable. This means you no longer have to care
|
||||
about implementing the CompilableInterface or a custom `compile()` function, and that every Fluid template can now be cached
|
||||
to a compiled PHP script regardless of ViewHelpers.
|
||||
|
||||
ViewHelpers still are able to define a custom `compile()` function but are no longer required to do so. When they don't define
|
||||
such a method, an execution is chosen which is identical in performance to calling the ViewHelper from a template that before
|
||||
this could not be compiled. The ViewHelpers that do define a custom compiling method can further increase performance.
|
||||
|
||||
When you explicitly require a ViewHelper of yours to prevent template caching it is possible to implement a custom `compile()`
|
||||
method which calls `$templateParser->disable();` and nothing else. Doing this disables the compiling inside the scope (template,
|
||||
partial or section) currently being rendered.
|
||||
|
||||
New and more efficient escaping
|
||||
-------------------------------
|
||||
|
||||
Contrary to earlier versions of Fluid which used a ViewHelperNode for `f:format.htmlentities` around other nodes it wished to
|
||||
escape, standalone Fluid has implemented a custom SyntaxTreeNode type which does the escaping in a more efficient manner
|
||||
(directly using `htmlentities`). Although it means you cannot override this escaping behaviour by overriding the
|
||||
`f:format.htmlentities` ViewHelper (which is completely possible to do with Fluid now) it should mean a significant boost to
|
||||
performance as it avoids an excessive amount of ViewHelper resolving and -rendering operations, replacing them with a single PHP
|
||||
function call wrapped in a tiny class, which compiles also to a single function call and which compiles in a way that it wraps
|
||||
the compiled output of the Node it escapes as a pure string operation.
|
||||
|
||||
Escaping interception is still contained within the `Configuration` instance given to the TemplateParser - and those can be
|
||||
manipulated with a custom RenderingContext (see above).
|
||||
|
||||
.. index:: Fluid, PHP-API, Frontend, Backend
|
||||
Reference in New Issue
Block a user