Pragmas are a way to extend the mustache syntax, as well as alter it. They are
invoked using the syntax {{%PRAGMA-NAME}}
.
Pragmas can affect both the lexing (tokenization/parsing) and rendering phases of templating. In the former case, they can be used to analyze tokens and potentially change them; in the latter, they can be used to alter how view data is processed and rendered.
Pragma names consist only of uppercase alphabetical characters and dashes.
Lets take the Implicit-Iterator defined in one section:
{{!template-with-pragma-in-section.mustache}}
Some content, with {{type}}
{{#section1}}
{{%IMPLICIT-ITERATOR}}
{{#subsection}}
{{.}}
{{/subsection}}
{{/section1}}
{{#section2}}
{{#subsection}}
{{.}}
{{/subsection}}
{{/section2}}
Note that the pragma is only defined in section1
.
Now consider the following view:
$mustache->getRenderer()->addPragma(new Phly\Mustache\Pragma\ImplicitIterator());
$test = $mustache->render('template-with-pragma-in-section', [
'type' => 'style',
'section1' => [
'subsection' => [1, 2, 3],
],
'section2' => [
'subsection' => [4, 5, 6],
],
]);
When the above is excecuted, only the contents of section1.subsection
will be
iterated; section2.subsection
will not. This results in the following output:
Some content, with style
1
2
3
Pragmas only apply to the specific template in which they are defined. This means that any partials or parent templates (if using template inheritance) are not affected.
As an example, consider the following templates:
{{!partial-with-section.mustache}}
This is from the partial
{{#section}}
{{#subsection}}
{{.}}
{{/subsection}}
{{/section}}
{{!template-with-pragma-and-partial.mustache}}
{{%IMPLICIT-ITERATOR}}
Some content, with {{type}}
{{>partial-with-section}}
And the following view:
$mustache->getRenderer()->addPragma(new Phly\Mustache\Pragma\ImplicitIterator());
$test = $mustache->render('template-with-pragma-and-partial', [
'type' => 'style',
'section' => [
'subsection' => [1, 2, 3],
],
]);
You can expect the following output:
Some content, with style
This is from the partial
Pragmas extend the behavior of Mustache, allowing users to opt-in to features
that exist outside the Mustache specification. phly-mustache provides
Phly\Mustache\Pragma\PragmaInterface
to allow you to define code that
implements these features.
namespace Phly\Mustache\Pragma;
use Phly\Mustache\Mustache;
interface PragmaInterface
{
/**
* Retrieve the name of the pragma
*
* @return string
*/
public function getName();
/**
* Whether or not this pragma can handle the given token
*
* @param int $token
* @return bool
*/
public function handlesToken($token);
/**
* Parse the provided token.
*
* If the pragma handles a given token, it is allowed to parse it; the
* lexer will call this method when the token has been created, passing the
* token struct.
*
* Token structs contain, minimally:
*
* - index 0: the token type (see the `Lexer::TOKEN_*` constants)
* - index 1: the related data for the token
*
* The method MUST return a token struct on completion; if the pragma does
* not need to do anything, it can simply `return $tokenStruct`.
*
* @param array $tokenStruct
* @return array
*/
public function parse(array $tokenStruct);
/**
* Render a given token.
*
* Returning an empty value returns control to the renderer.
*
* $tokenStruct is an array consisting minimally of:
*
* - 0: int token (from Lexer::TOKEN_* constants)
* - 1: mixed data (data associated with the token)
*
* @param array $tokenStruct
* @param mixed $view
* @param array $options
* @param Mustache $mustache Mustache instance handling rendering.
* @return mixed
*/
public function render(array $tokenStruct, $view, array $options, Mustache $mustache);
}
phly-mustache also provides a trait, Phly\Mustache\Pragma\PragmaNameAndTokensTrait
,
that you can use to simplify pragma development. use
the trait, and define the
properties $name
and $tokensHandled
, and you will only need to define the
render()
method at that point.
use Phly\Mustache\Lexer;
use Phly\Mustache\Mustache;
use Phly\Mustache\Pragma\PragmaInterface;
use Phly\Mustache\Pragma\PragmaNameAndTokensTrait;
class FooBarPragma implements PragmaInterface
{
use PragmaNameAndTokensTrait;
private $name = 'FOO-BAR';
private $tokensHandled = [
Lexer::TOKEN_VARIABLE,
];
public function parse(array $tokenStruct)
{
// ...
}
public function render(array $tokenStruct, $view, array $options, Mustache $mustache)
{
// ...
}
}
To use a pragma, you need to add an instance of it to the
Phly\Mustache\Pragma\PragmaCollection
instance composed by the Mustache
instance. You can retrieve it by calling the getPragmas()
method of the
Mustache
instance:
$pragmas = $mustache->getPragmas();
Once retrieved, you can perform the following operations:
add(PragmaInterface $pragma)
will attach a pragma, exposing it to templates. If the pragma has already been added, or another pragma with the same name has, it will raise an exception.has($pragma)
will tell you if a pragma with a given name is present in the collection.get($pragma)
will retrieve a pragma with a given name from the collection. If the pragma is not present in the collection, it will raise an exception.clear()
will remove all pragmas from the collection.
The collection is countable and iterable.
Normally, sections expect key/value pairs, but often when iterating, you will have simply a list of values.
The implicit iterator pragma allows iteration of indexed arrays or Traversable
objects with scalar values, with the option of specifying the iterator "key" to
use within the template. By default, a variable key "." will be replaced by the
current value of the iterator.
To assign the name, add the verbiage iterator=varname
when invoking the
pragma.
As an example:
{{!template-with-implicit-iterator.mustache}}
{{%IMPLICIT-ITERATOR iterator=bob}}
{{#foo}}
{{bob}}
{{/foo}}
The above will assign each list item in foo
to the variable bob
, which we
can then render.
Here's the related view:
$mustache->getRenderer()->addPragma(new Phly\Mustache\Pragma\ImplicitIterator());
$view = ['foo' => [1, 2, 3, 4, 5, 'french']];
$test = $mustache->render(
'template-with-implicit-iterator',
$view
);
The two together render the following:
1
2
3
4
5
french
We could have also used the default .
placeholder instead, which would have
resulted in the following template:
{{!template-with-implicit-iterator.mustache}}
{{%IMPLICIT-ITERATOR}}
{{#foo}}
{{.}}
{{/foo}}
The CONTEXTUAL-ESCAPE pragma allows you to specify an escaping context when specifying a variable in your template. Contexts include:
- html (default; you need not specify this)
- attr (for escaping HTML attribute values)
- js (for escaping JavaScript)
- css (for escaping CSS)
- url (for escaping URLs)
This allows the following:
{{%CONTEXTUAL-ESCAPE}}
<html>
<head>
<script>{{scripts|js}}</script>
<style>{{styles|css}}</script>
</head>
<body>
<article class="{{article_class|attr}}">
<a href="{{article_url|url}}">link</a>
</article>
</body>
</html>
In order to use the pragma, you must first register it with Mustache
:
use Phly\Mustache\Pragma\ContextualEscape;
$mustache->getPragmas()->add(new ContextualEscape());
The SUB-VIEWS pragma allows you to implement the two-step view pattern. When
active, any variable whose value is an instance of
Phly\Mustache\Pragma\SubView
will be substituted by rendering the template and
view that object encapsulates.
The SubView
class takes a template name and a view via the constructor:
use Phly\Mustache\Pragma\SubView;
$subView = new SubView('some-partial', ['name' => 'Matthew']);
That object is then assigned as a value to a view key:
$view = new stdClass;
$view->content = $subView;
The template might look like this:
{{!layout}}
{{%SUB-VIEWS}}
<html>
<body>
{{content}}
</body>
</html>
and the partial like this:
{{!some-partial}}
Hello, {{name}}!
Rendering the view:
use Phly\Mustache\Mustache;
use Phly\Mustache\Pragma\SubViews;
$mustache = new Mustache();
$subViews = new SubViews($mustache);
$rendered = $mustache->render('layout', $view);
will result in:
<html>
<body>
Hello, Matthew!
</body>
</html>
Sub views may be nested, and re-used.
Typically, you should use template inheritance instead whenever possible, as it is built-in, and easier to re-use.