diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7bea25ae5..b3101c671 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,5 +48,6 @@ jobs: - name: Documentation integrity run: | - FLUID_DOCUMENTATION_OUTPUT_DIR=Documentation/ViewHelpers vendor/bin/fluidDocumentation generate vendor/t3docs/fluid-documentation-generator/config/fluidStandalone/* + FLUID_DOCUMENTATION_OUTPUT_DIR=DocumentationTemp vendor/bin/fluidDocumentation generate vendor/t3docs/fluid-documentation-generator/config/fluidStandalone/* + cp -r DocumentationTemp/Fluid DocumentationTemp/Fluid.json Documentation/ViewHelpers/ && rm -r DocumentationTemp git add Documentation/*; git status; git status | grep -q "nothing to commit, working tree clean" diff --git a/Documentation/Development/Decisions.rst b/Documentation/Development/Decisions.rst deleted file mode 100644 index 2bc002c10..000000000 --- a/Documentation/Development/Decisions.rst +++ /dev/null @@ -1,146 +0,0 @@ -.. include:: /Includes.rst.txt - -.. _decisions: - -====================================== -Decoupling decisions and compatibility -====================================== - -This is a historical elaboration on some details when this project has been -decoupled form `TYPO3.Flow` and made a standalone library. Isn't of too much -help nowadays anymore, but can be helpful for some insight on why things -materialized as is. - -In order to decouple this package from `TYPO3.Flow` a few necessary but -implication-filled decisions had to be made. - -No more dependency injection -============================ - -It is simply gone and will not be implemented again in any shape or form. The DI -concept from `TYPO3.Flow` does not apply on a wider scale and it is every bit as -possible to achieve the same results through the constructor methods. While DI -is a nice concept for usability, it is better to sacrifice it for more universal -class support. - -No more widget support -====================== - -It is also gone and will not be implemented again. However: because of the -nature of Fluid and ViewHelpers, **recreating the implementation that behaves -like Widgets but does so in the context of the framework in which it gets used** -is the correct way to achieve this behavior. - -As an added benefit, all translated labels and a **lot** of functional tests and -fixtures can and have been removed. - -ViewHelpers can't use render method arguments -============================================= - -Although still technically feasible to implement, a decision was made to -sacrifice the support for `render()` method arguments in ViewHelpers -(as opposed to using `registerArgument` inside `initializeArguments`). -The following reasons are given: - -1. Render method type hinting beyond strict types is impossible without - analysing phpdoc comments which implies Reflection as well as a dependency - on the `TYPO3.Flow` annotation parser. -2. Performance and testability is exactly the same. -3. A single method to register arguments now exists and `ArgumentDefinition` can - be reduced in complexity. -4. Having only one method to check in every instance means better performance - overall. - -Overriding these capabilites is possible and implies a custom -`ViewHelperInvoker` returned from a custom `ViewHelperResolver`. - -The View consumes TemplatePaths -=============================== - -Rather than allow the View class to be aware of the Request via the -ControllerContext, both the Request and ControllerContext concepts have been -completely removed. A new strategy has taken the place of these: the -TemplatePaths. - -In order to instantiate a View instance, a special TemplatePaths instance must -now be passed as first argument. This TemplatePaths instance *is then -responsible for all the resolving and retrieval of template files*. This means -that replacing the TemplatePaths instance that gets used will allow another -implementation to **effectively change how Fluid resolves template files**. - -This also means that the View itself no longer has any file resolving -capabilities. Instead, it relies on the provided instance to resolve every file -and template source. For usability, there are dedicated methods to set template -path and filename as well as layout path and filename. - -**Because of this the path treatments are now completely decoupled and no longer -uses expression expansions to create arrays of expected paths**. Instead, each -path is iterated and the epected filename checked and returned if found. In -order to potentially re-implement this decoupled package into `TYPO3.Flow` as -well as `TYPO3.CMS` this means that these paths can then be generated by the MVC -and simply passed along to the View - or, íf more complex behavior is required, -a custom TemplatePaths implementation can be created. - -As a side effect of this, **the View no longer supports any options**. Where -before a user was able to pass paths, filenames and other settings as `options` -on the View, such behavior is no longer required due to the use of -TemplatePaths. However, such behavior can be restored by subclassing the -TemplateView and adding `options` support which simply delegates to -TemplatePaths to change root paths etc. - -Obviously your paths can no longer use any custom syntax or markers for -replacement such as `EXT:...` paths or `@marker` patterns. To use such -expressions make sure you convert the paths *before* you pass them to the View; -e.g. do so inside your custom TemplatePaths or before you pass the paths to the -built-in TemplatePaths object. - -Deprecated code has been evicted -================================ - -This includes all and every support for the legacy path name conventions - only -arrays of paths are now accepted except when specifying the full template- or -layout path and filename on the View. - -Note about injecting functionality -================================== - -It is possible to restore almost all of the removed features by creating custom -implementations of the following classes: - -* https://github.com/TYPO3Fluid/Fluid/blob/main/src/View/TemplatePaths.php to - change how template files are resolved. -* https://github.com/TYPO3Fluid/Fluid/blob/main/src/Core/ViewHelper/ViewHelperResolver.php - to change how each ViewHelper is resolved, how its arguments are retrieved, - which namespaces are available and expected class names of ViewHelpers. - -When combined in a package that implements `TYPO3.Fluid` in a framework, these -two classes together make it possible to change all the behaviors that were -changed to make `TYPO3.Fluid` more portable. The solutions are, in order: - -1. Dependency injection in ViewHelpers can be restored by overriding the - `createViewHelperClassInstance` method of the ViewHelperResolver - implementation. The View itself can of course also be loaded by an object - manager that does injection. -2. Widgets can be reintroduced as a framework-specific feature by making the - ViewHelperResolver return the desired class names when the TemplateParser - tries to load the ViewHelpers that were removed. This includes all of the - other ViewHelpers which were sacrificed for portability because they had too - strong dependencies on the framework; for example the `f:form` helpers known - from TYPO3 CMS and Flow. -3. The arguments of each ViewHelper can be adjusted - or the class itself can be - replaced. This allows the framework to replace those ViewHelpers that have - been "dumbed down" (for example the debug ViewHelper which in CMS or Flow - would use the DebuggerUtility to display variables but in this standalone - version uses a plain `var_dump`). -4. The TemplatePaths implementation can be replaced by one that supports the - necessary folder structures and path treatments, for example allowing the - `EXT:....` syntax in paths and making it possible to "bubble sub package" or - whatever special feature the template file resolving should support. -5. Deprecated code and support for legacy class names can be introduced by - overriding the ViewHelperResolver. The aliases can be defined any way - desired - as long as the ViewHelperResolver is aware of them and will return - the **new** class as replacement. -6. The way the ViewHelpers are themselves executed can be overridden via a - custom ViewHelperInvoker returned from the custom ViewHelperResolver. - Overriding this part allows, among other things, restoring the `render()` - method argument support. diff --git a/Documentation/Development/Expressions.rst b/Documentation/Development/Expressions.rst deleted file mode 100644 index d65820be1..000000000 --- a/Documentation/Development/Expressions.rst +++ /dev/null @@ -1,138 +0,0 @@ -.. include:: /Includes.rst.txt - -.. _creating-expressionnodes: - -======================== -Creating ExpressionNodes -======================== - -To understand what an ExpressionNode is and which cases can be solved by -implementing custom ones, first read the -:doc:`chapter about implementing Fluid `. Once you -grasp what an ExpressionNode is and how it works, a very brief example is all -you need. - -First: an ExpressionNode is always one PHP class. Where you place it is -completely up to you - but to have the class actually be detected and used by -Fluid, *the class name must be returned from a custom ViewHelperResolver*. -This concept is also explained in the implementation chapter. - -In Fluid's default ViewHelperResolver, the following code is responsible for -returning expression node class names: - -.. code-block:: php - - /** - * List of class names implementing ExpressionNodeInterface - * which will be consulted when an expression does not match - * any built-in parser expression types. - * - * @var string - */ - protected $expressionNodeTypes = [ - 'TYPO3Fluid\\Fluid\\Core\\Parser\\SyntaxTree\\Expression\\CastingExpressionNode', - 'TYPO3Fluid\\Fluid\\Core\\Parser\\SyntaxTree\\Expression\\MathExpressionNode', - 'TYPO3Fluid\\Fluid\\Core\\Parser\\SyntaxTree\\Expression\\TernaryExpressionNode', - ]; - - /** - * @return string - */ - public function getExpressionNodeTypes() - { - return $this->expressionNodeTypes; - } - -You may or may not want the listed expression nodes included, but if you change -the available expression types you should of course document this difference -about your implementation. - -The following are fairly normal ways of replacing or extending this array of -class names: - -* Override the property `$expressionNodeTypes` and define your own array -* Override the `getExpressionNodeTypes` method and return a complete array -* Override the `getExpressionNodeTypes` method and modify/append the array from - `parent::getExpressionNodeTypes` - -Once you are ready to create your ExpressionNode class, all you need is a very -simple one. The following class is the ternary ExpressionNode from Fluid itself -which detects the `{a ? b : c}` syntax and evaluates `a` as boolean and if true, -renders `b` else renders `c`. To get this behavior, we need a (relatively -simple) regular expression and one method to evaluate the expression while being -aware of the rendering context (which stores all variables, controller name, -action name etc). - -.. code-block:: php - - assign('variablename', 'value');`. 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 `TemplatePaths` 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 `layoutName` is reserved and can be assigned to a - template to set its Layout instead of using ``. - -TemplatePaths -============= - -In the default `TemplatePaths` object included with `TYPO3.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 `TemplatePaths` object if: - -1. You are able to place your template files in folders that match the - `TYPO3.Fluid` conventions, including the convention of subfolders named the - same as your controllers. -2. You are able to provide the template paths that get used as an array with - which `TemplatePaths` can be initialized. -3. Or you are able to individually set each group of paths. -4. You are able to rely on standard format handling (`format` simply being the - file extension of template files). - -And you should replace the `TemplatePaths` with your own subclass if: - -1. You answered no to any of the above. -2. You want to be able to deliver template content before parsing, from other - sources than files. -3. You want the resolving of template files for controller actions to happen in - a different way. -4. 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 `TemplatePaths` instance -*must be provided as first argument for the View*. - -RenderingContext -================ - -Because `TYPO3.Fluid` was created in an MVC context it supports MVC behaviors, -including setting a "context" for your rendering process - to associate the -rendering with a controller and an action. The default `RenderingContext` -provided by `TYPO3.Fluid` has limited support: it supports a controller name -and an action name. - -Should you require additional context variables - for example a package name, -a sub-controller identification, a user validation, the HTTP request object, -a Response object, or whatever - you can create your own type of -`RenderingContext` and pass that to the View. Doing this allows you to access -this `RenderingContext` from within ViewHelpers. One obvious purpose for this -is to *create custom links to controller actions*. - -You should use the default `RenderingContext` object if: - -1. You don't use an MVC context - you just render single templates possibly with - partials and layouts. -2. You use MVC and are able to rely on just a controller name and action name - for your implementation. -3. You can rely on the default way of storing template- and ViewHelper - variables. - -You should replace the `RenderingContext` with your own if: - -1. You answered no to any of the above. -2. You require additional (framework/implementation-specific) attributes on the - `RenderingContext`. -3. You require dynamic ways of returning the controller name, action name - (or other custom attributes). -4. You wish to modify or replace the special `VariableContainer` objects that - store variables to implement things like reserved variable names, persistent - and auto-added variables and similar container-related operations. - -Whether you use your own class or the default, the `RenderingContext` instance -*must be passed as second argument for the View*. If you do not pass a -`RenderingContext`, the default one will automatically be used. - -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 `FluidCacheInterface`-implementing object is -provided. `TYPO3.Fluid` provides one such caching implementation: the -`SimpleFileCache` which just stores compiled PHP code in a designated directory. - -Should you need to store the compiled templates in other ways you can implement -`FluidCacheInterface` in your caching object. - -Whether you use your own cache class or the default, the `FluidCache` -*must be passed as third parameter for the View* or it -*must be assigned using `$view->getRenderingContext()->setCache($cacheInstance)` -before calling `$view->render()`*. - -TemplateProcessor -================= - -While custom `TemplatePaths` also allows sources of template files to be -modified before they are given to the TemplateParser, a custom `TemplatePaths` -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 -`TemplateProcessorInterface` is provided. Implementing this interface and the -methods it designates allows your class to be passed to the `TemplateView` and -be triggered every time a template source is parsed, right before parsing -starts: - -.. code-block:: php - - $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 `preProcessSource($templateSource)` 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 for - example 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 `TemplatePaths` -implementation - but even with such a custom implementation this -TemplateProcessor pattern can still be used to manipulate/validate the sources -coming from `TemplatePaths`, providing a nice way to decouple paths resolving -from template source processing. - -ViewHelperInvoker -================= - -The `ViewHelperInvoker` is a class dedicated to validating current arguments of -and if valid, calling the ViewHelper's render method. The default object -supports only the arguments added via `initializeArguments` and -`(register|override)Argument` on the ViewHelper - and it does not use internal -instance caching; it creates and renders new ViewHelpers for every node. - -You should replace the `ViewHelperInvoker` if: - -1. You must support different ways of calling ViewHelpers such as alternative - `setArguments` names. -2. You wish to change the way the invoker uses and stores ViewHelper instances, - for example to use an internal cache. -3. You wish to change the way ViewHelper arguments are validated, for example - changing the Exceptions that are thrown. -4. 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. - -If you wish to use a custom `ViewHelperInvoker` you **must** do so via a custom -`ViewHelperResolver`. You are given the class name of the ViewHelper to resolve -a `ViewHelperInvoker` - which means you can also use different invokers for -different classes. - -.. _implementation-view-helper-resolver: - -ViewHelperResolver -================== - -In `TYPO3.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 connected to ViewHelpers. These are the special classes that are -called using for exampel -`{somestring}`. - -A ViewHelper is essentially referenced by the namespace and the path to the -ViewHelper, in this case `f` being the namespace and `format.htmlentities` being -the path. - -The `ViewHelperResolver` 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 `ViewHelperResolver` if: - -1. You can rely on the default way of turning a namespace and path of a - ViewHelper into a class name. -2. You can rely on the default way ViewHelpers return the arguments they - support. -3. You can rely on instantiation of ViewHelpers happening through a simple - `new $class()`. -4. You can rely on the default `ViewHelperInvoker`. - -You should replace the `ViewHelperResolver` if: - -1. You answered no to any of the above. -2. You want to make ViewHelper namespaces available in templates without - importing. -3. 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. -4. 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). -5. You have to use a custom `ViewHelperInvoker` to actually render your - ViewHelpers. - -The default `ViewHelperResolver` can be replaced in one way only: calling -`$view->setViewHelperResolver($resolverInstance);` on the TemplateView. However, -a custom View class can of course replace this and other aspects of the View -such as `TemplatePaths`. - -ExpressionNodes -=============== - -The `ExpressionNode` concept is the most profound way you can manipulate the -Fluid language itself, adding to it new syntax options that can be used inside -the shorthand `{...}` syntax. Normally you are confined to using ViewHelpers -when you want such special processing in your templates - but using -`ExpressionNodes` allows you to add these processings as actual parts of the -templating language itself; avoiding the need to include a ViewHelper namespace. - -`TYPO3.Fluid` itself provides the following types of `ExpressionNodes`: - -1. `MathExpressionNode` which scans for and evaluates simple mathematical - expressions like `{variable + 1}`. -2. `TernaryExpressionNode` which implements a ternary condition in Fluid syntax - like `{ifsomething ? thenoutputthis : elsethis}` -3. `CastingExpressionNode` which casts variables to a certain type, e.g. - `{suspectType as integer}`, `{myInteger as boolean}`. - -An `ExpressionNode` basically consists of one an expression matching pattern -(regex), one non-static method to evaluate the expression `public function -evaluate(RenderingContextInterface $renderingContext)` and a mirror of this -function which can be called statically: -`public static evaluteExpression(RenderingContextInterface $renderingContext, $expression)`. -The non-static method should then simply delegate to the static method and use -the expression stored in `$this->expression` as second parameter for the static -method call. - -`ExpressionNodes` automatically support compilation and will generate compiled -code which stores the expression and calls the static `evaluateExpression` -method with the rendering context and the stored expression. - -You should create your own `ExpressionNodes` if: - -1. You want a custom syntax in your Fluid templates (theoretical example: - casting variables using `{(integer)variablename}`). -2. You want to replace either of the above mentioned `ExpressionNodes` with ones - using the same, or an expanded version of their regular expression patterns - to further extend the strings they capture and process. - -**Limitations** - -1. Contrary to other nodes in Fluid, `ExpressionNodes` cannot be used in tag - form. Only the shorthand/inline syntax is supported. -2. `ExpressionNodes` are not recursive unless they have recursive behavior - internally (this is for example different from array nodes which match - sub-arrays recursively). In other words: `ExpressionNodes` are intended for - *simple syntaxes and variables*. - -To create a new type of `ExpressionNode` - perhaps one that fits your framework: - -1. Make sure you subclass - `TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\AbstractExpressionNode` - and implement `TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\Expression\ExpressionNodeInterface` - in your class. -2. Make sure your class pass `public static $detectionExpression = '%s';` where - `%s` is a perl regular expression which returns at least one match if the - expression can be handled by your `ExpressionNode` class. -3. Make sure your class implements a - `public static evaluateExpression(RenderingContextInterface $renderingContext, $expression)` - method which will be able to process the expression in a statically called - context. - -Any `ExpressionNode` types added this way are also compilable off-the-bat. The -one thing you can't change is how this compiling happens - so if your -`ExpressionNode` does some heavy processing you may consider implementing a -dedicated cache for it. - -Additional `ExpressionNode` class names can be returned from a custom -`ViewHelperResolver` (see above) by overriding the `getExpressionNodeTypes()` -method **and/or** the `protected $expressionTypes` property to append your class -names to the list. Each `ExpressionNode` is consulted in the order they appear -in this list - and only the first one that matches will be used. diff --git a/Documentation/Development/Index.rst b/Documentation/Development/Index.rst deleted file mode 100644 index 72d56a5d8..000000000 --- a/Documentation/Development/Index.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. include:: /Includes.rst.txt - -.. _development: - -=========== -Development -=========== - -.. toctree:: - :maxdepth: 2 - :titlesonly: - - Implementation - CreatingViewHelpers - Expressions - Decisions diff --git a/Documentation/Extending/ExpressionNodes.rst b/Documentation/Extending/ExpressionNodes.rst new file mode 100644 index 000000000..d15229a65 --- /dev/null +++ b/Documentation/Extending/ExpressionNodes.rst @@ -0,0 +1,167 @@ +.. include:: /Includes.rst.txt + +.. _creating-expressionnodes: + +======================== +Creating ExpressionNodes +======================== + +The :php:`ExpressionNode` concept is the most profound way you can manipulate the +Fluid language itself, adding to it new syntax options that can be used inside +the shorthand `{...}` syntax. Normally you are confined to using ViewHelpers +when you want such special processing in your templates - but using +ExpressionNodes allows you to add these processings as actual parts of the +templating language itself; avoiding the need to include a ViewHelper namespace. + +Fluid itself provides the following types of `ExpressionNodes`: + +1. :php:`MathExpressionNode` which scans for and evaluates simple mathematical + expressions like `{variable + 1}`. +2. :php:`TernaryExpressionNode` which implements a ternary condition in Fluid syntax + like `{ifsomething ? thenoutputthis : elsethis}` +3. :php:`CastingExpressionNode` which casts variables to a certain type, e.g. + `{suspectType as integer}`, `{myInteger as boolean}`. + +An :php:`ExpressionNode` basically consists of one an expression matching pattern +(regex), one non-static method to evaluate the expression +:php:`public function evaluate(RenderingContextInterface $renderingContext)` +and a mirror of this function which can be called statically: +:php:`public static evaluteExpression(RenderingContextInterface $renderingContext, $expression)`. +The non-static method should then simply delegate to the static method and use +the expression stored in `$this->expression` as second parameter for the static +method call. + +ExpressionNodes automatically support compilation and will generate compiled +code which stores the expression and calls the static :php:`evaluateExpression()` +method with the rendering context and the stored expression. + +You should create your own ExpressionNodes if: + +1. You want a custom syntax in your Fluid templates (theoretical example: + casting variables using `{(integer)variablename}`). +2. You want to replace either of the above mentioned :php:`ExpressionNodes` with ones + using the same, or an expanded version of their regular expression patterns + to further extend the strings they capture and process. + +.. _creating-expressionnodes-implementation: + +Implementation +============== + +An ExpressionNode is always one PHP class. Where you place it is +completely up to you - but to have the class actually be detected and used by +Fluid, it needs to be added to the rendering context by calling :php:`setExpressionNodeTypes()`. + +In Fluid's default :php:`RenderingContext`, the following code is responsible for +returning expression node class names: + +.. code-block:: php + + /** + * List of class names implementing ExpressionNodeInterface + * which will be consulted when an expression does not match + * any built-in parser expression types. + * + * @var string + */ + protected $expressionNodeTypes = [ + 'TYPO3Fluid\\Fluid\\Core\\Parser\\SyntaxTree\\Expression\\CastingExpressionNode', + 'TYPO3Fluid\\Fluid\\Core\\Parser\\SyntaxTree\\Expression\\MathExpressionNode', + 'TYPO3Fluid\\Fluid\\Core\\Parser\\SyntaxTree\\Expression\\TernaryExpressionNode', + ]; + + /** + * @return string + */ + public function getExpressionNodeTypes() + { + return $this->expressionNodeTypes; + } + +You may or may not want the listed expression nodes included, but if you change +the available expression types you should of course document this difference +about your implementation. + +The following class is the math ExpressionNode from Fluid itself +which detects the `{a + 1}` and other simple mathematical operations. +To get this behavior, we need a (relatively +simple) regular expression and one method to evaluate the expression while being +aware of the rendering context (which stores all variables, controller name, +action name etc). + +.. code-block:: php + + ` + :button-style: btn btn-secondary stretched-link + + .. card:: Creating ExpressionNodes + + Learn how to extend Fluid by creating custom expression syntax + + .. card-footer:: :ref:`Learn about ExpressionNodes ` + :button-style: btn btn-secondary stretched-link + +.. toctree:: + :hidden: + :titlesonly: + + ViewHelpers + ExpressionNodes diff --git a/Documentation/Development/CreatingViewHelpers.rst b/Documentation/Extending/ViewHelpers.rst similarity index 100% rename from Documentation/Development/CreatingViewHelpers.rst rename to Documentation/Extending/ViewHelpers.rst diff --git a/Documentation/Index.rst b/Documentation/Index.rst index 9e54f74aa..db601777e 100644 --- a/Documentation/Index.rst +++ b/Documentation/Index.rst @@ -14,8 +14,7 @@ Fluid Rendering Engine en :Author: - Claus Due, Sebastian Kurfürst, Karsten Dambekalns, Robert Lemke & Fluid - contributors + Fluid contributors :License: This document is published under the @@ -38,15 +37,33 @@ If using Fluid in combination with TYPO3 CMS, a look at the documentation of **Table of Contents:** -.. toctree:: - :maxdepth: 2 - :titlesonly: - - Installation/Index - Usage/Index - Development/Index - ViewHelpers/Index - Changelog/Index +.. toctree:: + :caption: About Fluid + :maxdepth: 1 + :titlesonly: + + Introduction/Index + Installation/Index + Usage/Index + Syntax/Index + +.. toctree:: + :caption: ViewHelper Reference + :maxdepth: 1 + :titlesonly: + + ViewHelpers/Fluid/Index + ViewHelpers/SchemaFiles + +.. toctree:: + :caption: Appendix + :maxdepth: 1 + :titlesonly: + + Extending/Index + Integrating/Index + Internals/Index + Changelog/Index .. Meta Menu diff --git a/Documentation/Installation/Index.rst b/Documentation/Installation/Index.rst index d0db92ac8..f35be11de 100644 --- a/Documentation/Installation/Index.rst +++ b/Documentation/Installation/Index.rst @@ -6,18 +6,13 @@ Installation ============ -1. Include as composer dependency using +Fluid is distributed through composer. It can be added to your project by +executing the following command: - .. code-block:: bash +.. code-block:: bash - composer require typo3fluid/fluid + composer require typo3fluid/fluid -2. Run - - .. code-block:: bash - - composer install - - to generate the vendor class autoloader. - -3. The classes from `TYPO3.Fluid` can now be used in your composer project. +To get started with Fluid, take a look at the +:ref:`Fluid Syntax ` as well as the +:ref:`Getting Started Guide `. diff --git a/Documentation/Integrating/Index.rst b/Documentation/Integrating/Index.rst new file mode 100644 index 000000000..6dea23b5d --- /dev/null +++ b/Documentation/Integrating/Index.rst @@ -0,0 +1,255 @@ +.. include:: /Includes.rst.txt + +.. _implementations: +.. _integrating-fluid: + +================= +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: + +.. contents:: + +.. _templateview: + +TemplateView +============ + +A fairly standard View implementation. The default object expects +:php:`TemplatePaths` as constructor argument and has a handful of utility methods +like :php:`$view->assign('variablename', 'value');`. 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 `TemplatePaths` the View requires, if it +needs a :ref:`custom ViewHelperResolver `, +if it must have some default variables, if it should have a default cache, etc. + +.. note:: + + The special variable `layoutName` is reserved and can be assigned to a + template to set its Layout instead of using ``. + +.. _templatepaths: + +TemplatePaths +============= + +In the default :php:`TemplatePaths` 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 :php:`TemplatePaths` object if: + +1. 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. +2. You are able to provide the template paths that get used as an array with + which :php:`TemplatePaths` can be initialized. +3. Or you are able to individually set each group of paths. +4. You are able to rely on standard format handling (`format` simply being the + file extension of template files). + +And you should replace the :php:`TemplatePaths` with your own subclass if: + +1. You answered no to any of the above. +2. You want to be able to deliver template content before parsing, from other + sources than files. +3. You want the resolving of template files for controller actions to happen in + a different way. +4. 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 :php:`TemplatePaths` instance +*must be provided as first argument for the View*. + +.. _renderingcontext: + +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 :php:`TemplateParser`, the :php:`TemplateCompiler`, +a :php:`StandardVariableProvider` or the :php:`TemplatePaths` mentioned above. +It also contains information about the current template context, somewhat +confusingly stored in :php:`controllerName` and :php:`controllerAction` 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: + +.. code-block:: php + + $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 `RenderingContext` +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 :php:`StandardVariableProvider`. + +With further refactoring, we try to provide better ways for these use cases in the future. + +.. _fluidcache: + +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 :php:`FluidCacheInterface`-implementing object is +provided. Fluid provides one such caching implementation: the +:php:`SimpleFileCache` which just stores compiled PHP code in a designated directory. + +Should you need to store the compiled templates in other ways you can implement +:php:`FluidCacheInterface` in your caching object. + +Whether you use your own cache class or the default, the `FluidCache` +*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: + +ViewHelperInvoker +================= + +The :php:`ViewHelperInvoker` 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 :php:`initializeArguments()` and +:php:`registerArgument()` on the ViewHelper and provides all additional arguments +via :php:`handleAdditionalArguments()` to the ViewHelper class. By default, the +ViewHelper implementations throw an exception, but this handling can be overwritten, +as demonstrated by :php:`AbstractTagBasedViewHelper`. + +You should replace the :php:`ViewHelperInvoker` if: + +1. You must support different ways of calling ViewHelpers such as alternative + `setArguments` names. +2. You wish to change the way the invoker uses and stores ViewHelper instances, + for example to use an internal cache. +3. You wish to change the way ViewHelper arguments are validated, for example + changing the Exceptions that are thrown. +4. 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 + :ref:`ViewHelperResolver `. + +.. _implementation-view-helper-resolver: +.. _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 +:xml:`{somestring}`. + +A ViewHelper is essentially referenced by the namespace and the path to the +ViewHelper, in this case `f` being the namespace and `format.htmlentities` being +the path. + +The :php:`ViewHelperResolver` 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 :php:`ViewHelperResolver` if: + +1. You can rely on the default way of turning a namespace and path of a + ViewHelper into a class name. +2. You can rely on the default way ViewHelpers return the arguments they + support. +3. You can rely on instantiation of ViewHelpers happening through a simple + `new $class()`. + +You should replace the :php:`ViewHelperResolver` if: + +1. You answered no to any of the above. +2. You want to make ViewHelper namespaces available in templates without + importing. +3. You want to use the dependency injection of your framework to resolve + and instantiate ViewHelper objects. +4. 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. +5. 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 :php:`ViewHelperResolver` can be replaced on the rendering context by calling +:php:`$renderingContext->setViewHelperResolver($resolverInstance);`. + +.. _templateprocessor: + +TemplateProcessor +================= + +While custom :ref:`TemplatePaths ` also allows sources +of template files to be modified before they are given to the TemplateParser, a +custom :php:`TemplatePaths` 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 +:php:`TemplateProcessorInterface` is provided. Implementing this interface and the +methods it designates allows your class to be passed to the :php:`TemplateView` and +be triggered every time a template source is parsed, right before parsing +starts: + +.. code-block:: php + + $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 :php:`preProcessSource($templateSource)` 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 :php:`TemplatePaths` +implementation - but even with such a custom implementation this +TemplateProcessor pattern can still be used to manipulate/validate the sources +coming from :php:`TemplatePaths`, providing a nice way to decouple paths resolving +from template source processing. diff --git a/Documentation/Internals/Index.rst b/Documentation/Internals/Index.rst new file mode 100644 index 000000000..7738ad6f6 --- /dev/null +++ b/Documentation/Internals/Index.rst @@ -0,0 +1,13 @@ +.. include:: /Includes.rst.txt + +.. _fluid-internals: + +=============== +Fluid Internals +=============== + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + * diff --git a/Documentation/Usage/RunningExamples.rst b/Documentation/Internals/RunningExamples.rst similarity index 100% rename from Documentation/Usage/RunningExamples.rst rename to Documentation/Internals/RunningExamples.rst diff --git a/Documentation/Introduction/Index.rst b/Documentation/Introduction/Index.rst new file mode 100644 index 000000000..019133b9b --- /dev/null +++ b/Documentation/Introduction/Index.rst @@ -0,0 +1,25 @@ +.. include:: /Includes.rst.txt + +.. _introduction: + +============ +Introduction +============ + +Fluid is a PHP-based templating engine for web projects. In contrast to other +templating engines, it uses an XML-based syntax in its templates, which allows +template authors to apply their existing HTML knowledge in Fluid templates. + +Fluid originated in the TYPO3 and Neos ecosystem before it was extracted +from these projects into a separate PHP package. While its main usage nowadays +is within TYPO3 projects, it can also be used as an independent templating +language in PHP projects. + +In Fluid, all dynamic output is escaped by default, which makes the templating +engine secure by default. This prevents common XSS (Cross Site Scripting) +mistakes that can easily happen in HTML templates. + +Fluid comes with a range of so-called ViewHelpers that allow various formatting +and output modification directly in the template. Custom logic can be added +by providing custom ViewHelper implementations through a straightforward +PHP API. diff --git a/Documentation/Syntax/Comments.rst b/Documentation/Syntax/Comments.rst new file mode 100644 index 000000000..9eb09fb40 --- /dev/null +++ b/Documentation/Syntax/Comments.rst @@ -0,0 +1,33 @@ +.. include:: /Includes.rst.txt + +:navigation-title: Comments + +.. _comments-syntax: + +====================== +Fluid Syntax: Comments +====================== + +If you want to completely skip parts of your template, you can make use of +the :ref:` ViewHelper `. + +.. versionchanged:: Fluid 4.0 + The content of the :ref:` ViewHelper ` is removed + before parsing. It is no longer necessary to combine it with CDATA tags + to disable parsing. + +.. code-block:: xml + + + This will be ignored by the Fluid parser and will not appear in + the source code of the rendered template + + +You can also use the :ref:` ViewHelper ` to temporarily comment +out invalid Fluid syntax while debugging: + +.. code-block:: xml + + + + diff --git a/Documentation/Syntax/ConditionsBooleans.rst b/Documentation/Syntax/ConditionsBooleans.rst new file mode 100644 index 000000000..249086424 --- /dev/null +++ b/Documentation/Syntax/ConditionsBooleans.rst @@ -0,0 +1,91 @@ +.. include:: /Includes.rst.txt + +:navigation-title: Conditions & Booleans + +.. _conditions-syntax: + +=================================== +Fluid Syntax: Conditions & Booleans +=================================== + +.. _boolean-conditions: + +Boolean conditions +================== + +Boolean conditions are expressions that evaluate to true or false. + +Boolean conditions can be used as ViewHelper arguments, whenever the datatype +`boolean` is given, e.g. in the `condition` argument of the +:ref:` ViewHelper `. + +1. The expression can be a variable which is evaluated as follows: + + * number: evaluates to `true`, if is not `0`. + * array: evaluates to `true` if it contains at least one element + +2. The expression can be a statement consisting of: `term1 operator term2`, for + example `{variable} > 3` + + * The operator can be one of `>`, `>=`, `<`, `<=`, + `==`, `===`, `!=`, `!==` or `%`, + +3. The previous expressions can be combined with `||` (or) or `&&` (and). + + +Examples: + +.. code-block:: xml + + + ... + + + + + ... + + + ... + + + + + ... + + + +Example using the inline notation: + +.. code-block:: xml + +
+ ... +
+ +.. _boolean-literals: + +Boolean literals +================ + +.. versionadded:: Fluid 4.0 + The boolean literals `{true}` and `{false}` have been introduced. + +You can use the boolean literals `{true}` and `{false}` in ViewHelper calls. This works +both in tag and inline syntax: + +.. code-block:: xml + + + + {f:render(section: 'MySection', optional: true)} + +If a ViewHelper argument is defined as `boolean`, it is also possible to provide +values of different types, which will then be converted to boolean implicitly: + +.. code-block:: xml + + + +This can be used to remain compatible to Fluid 2, which didn't support boolean literals +in all cases. diff --git a/Documentation/Syntax/Escaping.rst b/Documentation/Syntax/Escaping.rst new file mode 100644 index 000000000..2b9f73d02 --- /dev/null +++ b/Documentation/Syntax/Escaping.rst @@ -0,0 +1,144 @@ +.. include:: /Includes.rst.txt + +:navigation-title: Escaping + +.. _escaping-syntax: + +====================== +Fluid Syntax: Escaping +====================== + +.. _escaping-behavior: + +Escaping behavior in inline syntax +================================== + +At a certain nesting level, `'` needs to be escaped with a backslash: + +.. code-block:: xml + + + + + +While escaping cannot be avoided in all cases, alternatives should always be +preferred to improve the readability of templates. Depending on the use cases, there +are different approaches to achieve this. + + +Passing variables to inline ViewHelpers +--------------------------------------- + +If only a variable is passed as a ViewHelper argument, the single quotes and curly braces +can be omitted: + +.. code-block:: xml + + {f:format.case(mode: 'upper', value: myVariable)} + + +Using ViewHelper chaining if possible +------------------------------------- + +A lot of ViewHelpers that perform changes on a single value also accept that value as +child value. This allows a much cleaner syntax if you combine multiple ViewHelpers for +one value: + +.. code-block:: xml + + {myVariable -> f:format.case(mode: 'upper') -> f:format.trim()} + +.. _escaping-workarounds: + +Workarounds for syntax collision with JS and CSS +================================================ + +While it is generally advisable to avoid inline JavaScript and CSS code within +Fluid templates, sometimes it might be unavoidable. This might lead to collisions +between Fluid's inline or variable syntax and the curly braces used in JavaScript +and CSS. + +Currently, there is no clean way to solve this due to limitations of Fluid's parser. +This would need a bigger rewrite, which is not yet feasible due to other more pressing +issues. However, there are workarounds that might be applicable in your use case. + +f:format.json ViewHelper +------------------------ + +If your goal is to create JSON in your template, you can create an object in Fluid +and then use the :ref:` ViewHelper ` +to generate valid JSON: + +.. code-block:: xml + +
+ + +This can also be used directly in JavaScript code: + +.. code-block:: xml + +