Flow
Fast PHP templating engine
Install / Use
/learn @nramenta/FlowREADME
Flow - Fast PHP Templating Engine
Introduction
Flow began life as a major fork of the original Twig templating engine by Armin Ronacher, which he made for [Chyrp], a now-defunct blogging engine. Flow features template inheritance, includes, macros, custom helpers, whitespace control and many little features that make writing templates enjoyable. Flow tries to give a consistent and coherent experience in writing clean templates. Flow compiles each template into its own PHP class; used in conjunction with PHP's OPcache, this makes Flow a very fast and efficient templating engine. Templates can be read from files, loaded from string arrays, or even from databases with relative ease.
Installation
The easiest way to install is by using [Composer]; the minimum composer.json configuration is:
{
"require": {
"flow/flow": "0.7.*"
}
}
Flow requires PHP 7 or newer.
Usage
Using Flow in your code is straight forward:
<?php
require 'path/to/vendor/autoload.php';
use Flow\Loader;
use Flow\Adapter\FileAdapter;
$flow = new Loader(
Loader::RECOMPILE_NORMAL,
new FileAdapter('path/to/templates'),
new FileAdapter('path/to/cache')
);
try {
$template = $flow->load('home.html');
$template->display([
'data_1' => 'My first data',
'data_2' => 'My second data',
]);
} catch (\Exception $e) {
// something went wrong!
die($e->getMessage());
}
The Loader constructor arguments are as follows:
mode: Recompilation mode.source:Flow\Adapterobject. See the section on loading templates from other sources near the bottom of this document.target:Flow\Adapterobject. See the section on loading templates from other sources near the bottom of this document.helpers: Array of custom helpers.
The mode option can be one of the following:
Loader::RECOMPILE_NEVER: Never recompile an already compiled template.Loader::RECOMPILE_NORMAL: Only recompile if the compiled template is older than the source file due to modifications.Loader::RECOMPILE_ALWAYS: Always recompile whenever possible.
The default mode is Loader::RECOMPILE_NORMAL. If a template has never been
compiled, or the compiled PHP file is missing, the Loader will compile it once
regardless of what the current mode is.
In a typical development environment, the Loader::RECOMPILE_NORMAL mode should
be used, while the Loader::RECOMPILE_NEVER mode should be used for production
whenever possible. The Loader::RECOMPILE_ALWAYS mode is used only for internal
debugging and testing purposes by the developers and should generally be avoided.
Two kinds of exceptions are thrown by Flow: SyntaxError for syntax errors, and
RuntimeException for everything else.
Any reference to template files outside the source directory is considered to
be an error.
Basic concepts
Flow uses {% and %} to delimit block tags. Block tags are used mainly for
block declarations in template inheritance and control structures. Examples of
block tags are block, for, and if. Some block tags may have a body
segment. They're usually enclosed by a corresponding end<tag> tag. Flow uses
{{ and }} to delimit output tags, {! and !} to delimit raw output tags,
and {# and #} to delimit comments. Keywords and identifiers are
case-sensitive.
Comments
Use {# and #} to delimit comments:
{# This is a comment. It will be ignored. #}
Comments may span multiple lines but cannot be nested; they will be completely removed from the resulting output.
Expression output
To output a literal, variable, or any kind of expression, use the opening {{
and the closing }} tags:
Hello, {{ username }}
{{ "Welcome back, " ~ username }}
{{ "Two plus two equals " ~ 2 + 2 }}
Raw expression output
To output a raw expression without doing any output escaping, use the opening
{! and the closing !} tags:
The following will be HTML bold: {! "<b>bold text</b>" !}
Literals
There are several types of literals: numbers, strings, booleans, arrays, and
null.
Numbers
Numbers can be integers or floats:
{{ 42 }} and {{ 3.14 }}
Large numbers can be separated by underscores to make it more readable:
Price: {{ 12_000 | number_format }} USD
The exact placing of _ is insignificant, although the first character must be a digit; any _ character inside numbers will be removed. Numbers are translated into PHP numbers and thus are limited by how PHP handles numbers with regards to upper/lower limits and precision. Complex numeric and monetary operations should be done in PHP using the GMP extension or the bcmath extension instead.
Strings
Strings can either be double quoted or single quoted; both recognize escape sequence characters. There are no support for variable extrapolation. Use string concatenation instead:
{{ "This is a string " ~ 'This is also a string' }}
You can also join two or more strings or scalars using the join operator:
{{ "Welcome," .. user.name }}
The join operator uses a single space character to join strings together.
Booleans
{{ true }} or {{ false }}
When printed or concatenated, true will be converted to 1 while false will
be converted to an empty string. This behavior is consistent with the way PHP
treats booleans in a string context.
Arrays
{{ ["this", "is", "an", "array"][0] }}
Arrays are also hash tables just like in PHP:
{{ ["foo" => "bar", 'oof' => 'rab']['foo'] }}
Printing arrays will cause a PHP notice to be thrown; use the join helper:
{{ [1,2,3] | join(', ') }}
Nulls
{{ null }}
When printed or concatenated, null will be converted to an empty string. This
behavior is consistent with the way PHP treats nulls in a string context.
Operators
In addition to short-circuiting, boolean operators or and and returns one
of their operands. This means you can, for example, do the following:
Status: {{ user.status or "default value" }}
Note that the strings '0' and '' are considered to be false. See the section
on branching for more information. This behavior is consistent with the way PHP
treats strings in a boolean context.
Comparison operators can take multiple operands:
{% if 1 <= x <= 10 %}
<p>x is between 1 and 10 inclusive.</p>
{% endif %}
Which is equivalent to:
{% if 1 <= x and x <= 10 %}
<p>x is between 1 and 10 inclusive.</p>
{% endif %}
The in operator works with arrays, iterators and plain objects:
{% if 1 in [1,2,3] %}
1 is definitely in 1,2,3
{% endif %}
{% if 1 not in [4,5,6] %}
1 is definitely not in 4,5,6
{% endif %}
For iterators and plain objects, the in operator first converts them using a
simple (array) type conversion.
Use ~ (tilde) to concatenate between two or more scalars as strings:
{{ "Hello," ~ " World!" }}
String concatenation has a lower precedence than arithmetic operators:
{{ "1 + 1 = " ~ 1 + 1 ~ " and everything is OK again!" }}
Will yield
1 + 1 = 2 and everything is OK again!
Use .. (a double dot) to join two or more scalars as string using a single
space character:
{{ "Welcome," .. user.name }}
String output, concatenations and joins coerce scalar values into strings.
Operator precedence
Below is a list of all operators in Flow sorted and listed according to their precedence in descending order:
- Attribute access:
.and[]for objects and arrays - Filter chaining:
| - Arithmetic: unary
-and+,%,/,*,-,+ - Concatenation:
..,~ - Comparison:
!==,===,==,!=,<>,<,>,>=,<= - Conditional:
in,not,and,or,xor - Ternary:
? :
You can group subexpressions in parentheses to override the precedence rule.
Attribute access
Objects
You can access an object's member variables or methods using the . operator:
{{ user.name }}
{{ user.get_full_name() }}
When calling an object's method, the parentheses are optional when there are no arguments passed. The full semantics of object attribute access are as follows:
For attribute access without parentheses, in order of priority:
- If the attribute is an accessible member variable, return its value.
- If the object implements
__get, invoke and return its value. - If the attribute is a callable method, call and return its value.
- If the object implements
__call, invoke and return its value. - Return null.
For attribute access with parentheses, in order of priority:
- If the attribute is a callable method, call and return its value.
- If the object implements
__call, invoke and return its value. - Return null.
You can always force a method call by using parentheses.
Arrays
You can return an element of an array using either the . operator or the [
and ] operator:
{{ user.name }} is the same as {{ user['name'] }}
{{ users[0] }}
The . operator is more restrictive: only tokens of name type can be used as
the attribute. Tokens of name type begins with an alphabet or an underscore and
can only contain alphanumeric and underscore characters, just like PHP variables
and function names.
One special attribute access rule for arrays is the ability to invoke closure functions stored in arrays:
<?php
$template = $flow->load('my_template.html');
$template->display([
'user' => [
'firstname' => 'Rasmus',
'lastname' => 'Lerdorf',
'fullname' => function($self) {
return $self['firstname'] . ' ' . $self['lastname'];
},
],
]);
And call the fullname "method" in the template as follows:
{{ user.fullname }}
When invoked this way, the closure function will implicitly be passed the array it's in as the first argument. Extra arguments will be passed on to the closure function as the second and consecutive arguments. This rule lets you have arrays that behave not unlike
