Integrating Fluid
Fluid provides a standard implementation which works great on simple MVC frameworks and as standalone rendering engine. However, the standard implementation may lack certain features needed by the product into which you are integrating Fluid.
To make sure you are able to override key behaviors of Fluid the package will delegate much of the resolving, instantiation, argument mapping and rendering of ViewHelpers to special classes which can be both manipulated and overridden by the user. These special classes and their use cases are:
TemplateView
A fairly standard View implementation. The default object expects
Template
as constructor argument and has a handful of utility methods
like $view->assign
. Custom View types can be
implemented by subclassing the default class - but in order to avoid
problems, make sure you also call the original class' constructor method.
Creating a custom View allows you to change just a few aspects, mainly about
composition: which implementations of Template
the View requires, if it
needs a custom ViewHelperResolver,
if it must have some default variables, if it should have a default cache, etc.
Note
The special variable layout
is reserved and can be assigned to a
template to set its Layout instead of using <f:
.
TemplatePaths
In the default Template
object included with Fluid we provide a
set of conventions for resolving the template files that go into rendering a
Fluid template - the templates themselves, plus partials and layouts.
You should use the default Template
object if:
- You are able to place your template files in folders that match the Fluid conventions, including the convention of subfolders named the same as your controllers.
- You are able to provide the template paths that get used as an array with
which
Template
can be initialized.Paths - Or you are able to individually set each group of paths.
- You are able to rely on standard format handling (
format
simply being the file extension of template files).
And you should replace the Template
with your own subclass if:
- You answered no to any of the above.
- You want to be able to deliver template content before parsing, from other sources than files.
- You want the resolving of template files for controller actions to happen in a different way.
- You want to create other (caching-) identifiers for your partials, layouts and templates than defaults.
Whether you use your own class or the default, the Template
instance
must be provided as first argument for the View.
RenderingContext
The rendering context is the state object in Fluid's rendering process.
By default, it contains references to all other objects that are relevant
in the rendering process, such as the Template
, the Template
,
a Standard
or the Template
mentioned above.
It also contains information about the current template context, somewhat
confusingly stored in controller
and controller
due to the
MVC origins of Fluid.
Since Fluid 2.14, it is also possible to add arbitrary data to the rendering context, which obsoletes most cases where you would have to override the rendering context implementation in Fluid integrations:
$myCustomState = new \Vendor\Package\MyClass::class();
$view->getRenderingContext()->setAttribute(\Vendor\Package\MyClass::class, $myCustomState);
If at all possible, it should be avoided to use a custom Rendering
implementation. However, currently it might still be necessary for some cases,
for example if you want to replace the default implementation of one of the other
dependencies, such as the Standard
.
With further refactoring, we try to provide better ways for these use cases in the future.
FluidCache
The caching of Fluid templates happens by compiling the templates to PHP files
which execute much faster than a parsed template ever could. These compiled
templates can only be stored if a Fluid
-implementing object is
provided. Fluid provides one such caching implementation: the
Simple
which just stores compiled PHP code in a designated directory.
Should you need to store the compiled templates in other ways you can implement
Fluid
in your caching object.
Whether you use your own cache class or the default, the Fluid
must be passed as third parameter for the View or it
must be assigned using :php:`$view->getRenderingContext()->setCache($cacheInstance)`
before calling :php:`$view->render()`.
ViewHelperInvoker
The View
is a class dedicated to validating current arguments of
and if valid, calling the ViewHelper's render method. It is the primary API to
execute a ViewHelper from within PHP code. The default object
supports the arguments added via initialize
and
register
on the ViewHelper and provides all additional arguments
via handle
to the ViewHelper class. By default, the
ViewHelper implementations throw an exception, but this handling can be overwritten,
as demonstrated by Abstract
.
You should replace the View
if:
- You must support different ways of calling ViewHelpers such as alternative
set
names.Arguments - You wish to change the way the invoker uses and stores ViewHelper instances, for example to use an internal cache.
- You wish to change the way ViewHelper arguments are validated, for example changing the Exceptions that are thrown.
- You wish to perform processing on the output of ViewHelpers, for example to remove XSS attempts according to your own rules.
Note
ViewHelper instance creation and argument retrieval is handled by the ViewHelperResolver.
ViewHelperResolver
In Fluid most of your options for extending the language - for example,
adding new ways to format strings, to make special condition types, custom links
and such - are implemented as ViewHelpers. These are the special classes that are
called using for example
<f:
.
A ViewHelper is essentially referenced by the namespace and the path to the
ViewHelper, in this case f
being the namespace and format.
being
the path.
The View
is the class responsible for turning these two pieces
of information into an expected class name and when this class is resolved, to
retrieve from it the arguments you can use for each ViewHelper.
You should use the default View
if:
- You can rely on the default way of turning a namespace and path of a ViewHelper into a class name.
- You can rely on the default way ViewHelpers return the arguments they support.
- You can rely on instantiation of ViewHelpers happening through a simple
new $class
.()
You should replace the View
if:
- You answered no to any of the above.
- You want to make ViewHelper namespaces available in templates without importing.
- You want to use the dependency injection of your framework to resolve and instantiate ViewHelper objects.
- You want to change which class is resolved from a given namespace and ViewHelper path, for example allowing you to add your own ViewHelpers to the default namespace or replace default ViewHelpers with your own.
- You want to change the argument retrieval from ViewHelpers or you want to manipulate the arguments (for example, giving them a default value, making them optional, changing their data type).
The default View
can be replaced on the rendering context by calling
$rendering
.
TemplateProcessor
While custom TemplatePaths also allows sources
of template files to be modified before they are given to the TemplateParser, a
custom Template
implementation is sometimes overkill - and has the drawback
of completely overruling the reading of template file sources and making it up to
the custom class how exactly this processing happens.
In order to allow a more readily accessible and flexible way of pre-processing
template sources and affect key aspects of the parsing process, a
Template
is provided. Implementing this interface and the
methods it designates allows your class to be passed to the Template
and
be triggered every time a template source is parsed, right before parsing
starts:
$myTemplateProcessor = new MyTemplateProcessor();
$myTemplateProcessor->setDoMyMagicThing(true);
$templateView->setTemplateProcessors([
$myTemplateProcessor
]);
The registration method requires an array - this is to let you define multiple processors without needing to wrap them in a single class as well as reuse validation/manipulation across frameworks and only replace the parts that need to be replaced.
This makes the method pre
be called on this
class every time the TemplateParser is asked to parse a Fluid template.
Modifying the source and returning it makes that new template source be used.
Inside the TemplateProcessor method you have access to the TemplateParser and
ViewHelperResolver instances which the View uses.
The result is that TemplateProcessor instances are able to, for example:
- Validate template sources and implement reporting/logging of errors in a framework.
- Fix things like character encoding issues in template sources.
- Process Fluid code from potentially untrusted sources, for example doing XSS removals before parsing.
- Extract legacy namespace definitions and assign those to the ViewHelperResolver for active use.
- Extract legacy escaping instruction headers and assign those to the TemplateParser's Configuration instance.
- Enable the use of custom template code in file's header, extracted and used by a framework.
Note again: these same behaviors are possible using a custom Template
implementation - but even with such a custom implementation this
TemplateProcessor pattern can still be used to manipulate/validate the sources
coming from Template
, providing a nice way to decouple paths resolving
from template source processing.