11. Twig is...
• The best template engine for PHP.
• Fast, secure and modern.
• Easy to learn, to read and to write.
• If you don’t know Twig yet, read the
official docs at:
http://twig.sensiolabs.org/documentation
31. Named arguments
{{ "now"|date("Y-m-d", "America/New_York") }}
mandatory to set the
second argument
{{ "now"|date(timezone="America/New_York") }}
just set the argument
you need
33. Functions and filters before 1.12
$twig->addFunction('functionName',
new Twig_Function_Function('someFunction')
);
$twig->addFunction('otherFunction',
new Twig_Function_Method($this, 'someMethod')
);
42. Overriding filters
• Where can I find the PHP function
used by Twig?
• How can I override it with my own
implementation?
43. Where Twig defines everything
lib/twig/Extension/Core.php
class Twig_Extension_Core extends Twig_Extension {
public function getTokenParsers() {
return array(
+1,300 lines
new Twig_TokenParser_For(),
new Twig_TokenParser_If(),
class!
new Twig_TokenParser_Extends(),
new Twig_TokenParser_Include(),
new Twig_TokenParser_Block(),
// ...
);
}
public function getFilters() {
$filters = array(
'format' => new Twig_Filter_Function('sprintf'),
'replace' => new Twig_Filter_Function('strtr'),
'abs' => new Twig_Filter_Function('abs'),
// ...
);
44. Where Twig defines everything
lib/twig/Extension/Core.php
class Twig_Extension_Core extends Twig_Extension {
public function getTokenParsers() {
return array( • Filters
new Twig_TokenParser_For(),
new Twig_TokenParser_If(),
new Twig_TokenParser_Extends(), • Functions
new Twig_TokenParser_Include(),
new Twig_TokenParser_Block(),
// ...
• Tags
• Operators
);
}
public function getFilters() {
$filters = array( • Tests
'format' => new Twig_Filter_Function('sprintf'),
'replace' => new Twig_Filter_Function('strtr'),
'abs' => new Twig_Filter_Function('abs'),
// ...
);
45. «sort» filter uses «asort» function
new Twig_SimpleFilter('sort', 'twig_sort_filter'),
// ...
function twig_sort_filter($array)
{
asort($array);
return $array;
}
46. Overriding filters
✔ Where can I find the PHP function
•
used by Twig?
• How can I override it with my own
implementation?
48. 1. Define a new Twig extension
class MyCoreExtension
extends Twig_Extension_Core {
// ...
}
49. 2. Define the new «sort» filter
class MyCoreExtension extends Twig_Extension_Core {
public function getFilters() {
// ...
}
}
50. 2. Define the new «sort» filter
class MyCoreExtension extends Twig_Extension_Core {
public function getFilters() {
return array_merge(
parent::getFilters(),
array( ... )
);
}
}
51. 2. Define the new «sort» filter
class MyCoreExtension extends Twig_Extension_Core
{
public function getFilters()
{
return array_merge(parent::getFilters(), array(
'sort' => new Twig_Filter_Method($this, 'sortFilter')
));
}
public function sortFilter($array)
{
natcasesort($array);
return $array;
}
}
52. 3. Register the new extension
$twig = new Twig_Environment( ... );
$twig->addExtension(
new MyCoreExtension()
);
53. This is now
natcasesort
{% for i in array|sort %}
{# ... #}
{% endfor %}
60. Variable functions
$twig->addFunction(
'the_*',
new Twig_Function_Function('wordpress')
); don’t use regexps,
just asterisks
function wordpress($property, $options)
{
// ...
}
61. Variable functions in practice
{{ the_ID() }}
function wordpress('ID') { ... }
{{ the_content() }}
function wordpress('content') { ... }
62. Variable functions in practice
{{ the_title('<h3>', '</h3>') }}
function wordpress(
'title',
array('<h3>', '</h3>')
) { ... }
73. The new «source» tag
{% source 'home.twig' %}
{% source '../../../composer.json' %}
74. How does Twig work internally
{% source
'simple.twig' %}
Twig PHP class
__TwigTemplate_06dff1ec7c2c
ceb3f45ac76fc059b730
template file
extends Twig_Template
{
public function
{# ... #} __construct(Twig_Environment
$env)
{
parent::__construct($env);
$this->parent = $this-
>env-
>loadTemplate("layout.twig");
$this->blocks = array(
Lexer Parser Compiler
75. How does Twig work internally
{% source
'simple.twig' %}
Twig PHP class
__TwigTemplate_06dff1ec7c2c
ceb3f45ac76fc059b730
template file
extends Twig_Template
{
public function
{# ... #} __construct(Twig_Environment
you must
$env)
{
parent::__construct($env);
provide these
$this->parent = $this-
>env-
>loadTemplate("layout.twig");
$this->blocks = array(
Lexer Parser Compiler
76. 1. Create a new token parser
class SourceTokenParser extends Twig_TokenParser
{
public function getTag()
{
return 'source';
}
}
77. 2. Register the new token parser
$loader = new Twig_Loader_Filesystem(...);
$twig = new Twig_Environment($loader, array(...));
$twig->addTokenParser(
new SourceTokenParser()
);
78. 3. Fill in the «parse» method
class SourceTokenParser extends Twig_TokenParser
{
public function parse(Twig_Token $token)
{
$lineno = $token->getLine();
$value = $this->parser->getExpressionParser()
->parseExpression();
$this->parser->getStream()
->expect(Twig_Token::BLOCK_END_TYPE);
return new SourceNode($value, $lineno, $this->getTag());
}
}
79. 3. Fill in the «parse» method
class SourceTokenParser extends Twig_TokenParser
{
public function parse(Twig_Token $token)
{ this is hard
$lineno = $token->getLine();
$value = $this->parser->getExpressionParser()
->parseExpression();
$this->parser->getStream()
->expect(Twig_Token::BLOCK_END_TYPE);
return new SourceNode($value, $lineno, $this->getTag());
}
}
80. 4. Define the node class that compiles tags
class SourceNode extends Twig_Node {
public function __construct(Twig_Node_Expression $value, $lineno, $tag = null) {
parent::__construct(array('file' => $value), array(), $lineno, $tag);
}
public function compile(Twig_Compiler $compiler) {
$compiler
-> // ...
->write('echo file_get_contents(')
->subcompile($this->getNode('file'))
->raw(');')
;
}
}
81. 4. Define the node class that compiles tags
class SourceNode extends Twig_Node {
public function __construct(Twig_Node_Expression $value, $lineno, $tag = null) {
parent::__construct(array('file' => $value), array(), $lineno, $tag);
}
public function compile(Twig_Compiler $compiler) {
$compiler
-> // ... this is
->write('echo file_get_contents(') very hard
->subcompile($this->getNode('file'))
->raw(');')
;
}
}
82. The compiled PHP template
// line 3
echo file_get_contents("simple.twig");
// ...
{% source 'simple.twig' %}
// line 5
echo file_get_contents("../../../composer.json");
84. Most apps use a single loader
$loader = new Twig_Loader_Filesystem(
__DIR__.'/templates'
);
$twig = new Twig_Environment($loader, array());
$html = $twig->render('home.html.twig');
85. Chaining several loaders
$loader1 = new Twig_Loader_Filesystem(...);
$loader2 = new Twig_Loader_Filesystem(...);
$loader = new Twig_Loader_Chain(array(
$loader1, $loader2
));
$twig = new Twig_Environment($loader, array());
// ...
86. Chaining different loaders
$loader1 = new Twig_Loader_Filesystem(...);
$loader2 = new Twig_Loader_Array(...);
$loader3 = new Twig_Loader_String(...);
$loader = new Twig_Loader_Chain(array(
$loader1, $loader2, $loader3
));
// ...
87. Chaining different loaders
$loader1 = new Twig_Loader_Filesystem(...);
$loader2 = new Twig_Loader_Array(...);
$loader3 = new Twig_Loader_String(...);
$loader = new Twig_Loader_Chain(array(
$loader1, $loader2, $loader3
));
order matters!
// ...
88. Adding loaders at runtime
$loader = new Twig_Loader_Filesystem('/templates');
$twig = new Twig_Environment($loader, array());
if ( ... ) {
$twig->addLoader(
new Twig_Loader_Filesystem('/special_templates')
);
}
// ...
89. Storing templates in several folders
$loader = new Twig_Loader_Filesystem(array(
__DIR__.'/default',
__DIR__.'/templates',
__DIR__.'/themes'
));
$twig = new Twig_Environment($loader, array());
// ...
90. Storing templates in several folders
$loader = new Twig_Loader_Filesystem(array(
__DIR__.'/default',
__DIR__.'/templates',
__DIR__.'/themes'
));
$twig = new Twig_Environment($loader,went wrong.
Whoops, looks like something array());
// ... Twig_Error_Loader: The "_DIR_/templates"
directory does not exist.
91. Adding folders at runtime
$loader = new Twig_Loader_Filesystem('/templates');
$path = $user_slug.'/templates';
if (file_exists($path)) {
$loader->addPath($path);
}
$twig = new Twig_Environment($loader, array());
// ...
92. Prioritizing template folders
$loader = new Twig_Loader_Filesystem('/templates');
if(...) {
$loader->addPath($user_slug.'/templates');
$loader->prependPath($user_slug.'/themes');
}
$twig = new Twig_Environment($loader, array());
// ...
93. Prioritizing template folders
$loader = new Twig_Loader_Filesystem('/templates');
if(...) {
$loader->addPath($user_slug.'/templates');
$loader->prependPath($user_slug.'/themes');
} path is added before
any other path
$twig = new Twig_Environment($loader, array());
// ...
96. Namespaces are better than folders
templates/
themes/index.twig
admin/index.twig
frontend/index.twig
97. Namespaces are better than folders
$loader = new Twig_Loader_Filesystem(__DIR__.'/templates');
$twig = new Twig_Environment($loader, array());
$html = $twig->render('admin/index.twig');
$html = $twig->render('themes/index.twig');
$html = $twig->render('frontend/index.twig');
98. App doesn’t work if folders change
templates/
themes/index.twig
frontend/index.twig
admin/
99. App doesn’t work if folders change
templates/
themes/index.twig
Whoops, looks like something went wrong.
frontend/index.twig
Twig_Error_Loader: The "admin/index.twig"
admin/
template does not exist.
100. Registering and using namespaces
$loader = new Twig_Loader_Filesystem(__DIR__.'/templates');
$loader->addPath(__DIR__.'/admin', 'admin');
$twig = new Twig_Environment($loader, array());
$html = $twig->render('@admin/index.twig');
$html = $twig->render('admin/index.twig');
101. Registering and using namespaces
$loader = new Twig_Loader_Filesystem(__DIR__.'/templates');
$loader->addPath(__DIR__.'/admin', 'admin');
$twig = new Twig_Environment($loader, array());
$html = $twig->render('@admin/index.twig');
$html = $twig->render('admin/index.twig');
102. Registering and using namespaces
$loader = new Twig_Loader_Filesystem(__DIR__.'/templates');
$loader->addPath(__DIR__.'/admin', 'admin');
$twig = new Twig_Environment($loader, array());
$html = $twig->render('@admin/index.twig');
$html = $twig->render('admin/index.twig');
103. Registering and using namespaces
$loader = new Twig_Loader_Filesystem(__DIR__.'/templates');
$loader->addPath(__DIR__.'/admin', 'admin');
$twig = new Twig_Environment($loader, array());
$html = $twig->render('@admin/index.twig');
$html = $twig->render('admin/index.twig');
logical path
physical path
104. A practical use case
$loader->addPath(
__DIR__.'/themes/default/admin', 'backend'
);
// with namespaces
$html = $twig->render('@backend/edit.twig');
// with physical paths
$html = $twig->render('themes/default/admin/
edit.twig');
109. Twig.js = Twig inside JavaScript
TWIG
TWIG
HTML HTML
JS
JS
110. Twig.js = Twig inside JavaScript
TWIG
TWIG
HTML HTML
JS
JS TWIG
TWIG
111. «A tale of two twig.js»
twig.js by
Johannes Schmitt
http://github.com/schmittjoh/twig.js
twig.js by
John Roepke
https://github.com/justjohn/twig.js
112. «A tale of two twig.js»
twig.js by
Johannes Schmitt
http://github.com/schmittjoh/twig.js
twig.js by
John Roepke
https://github.com/justjohn/twig.js
113. twig.js by Johannes Schmitt
• A templating engine for Javascript
using Jinja/Twig style syntax.
• It compiles your Twig templates to
raw Javascript (to use the same
templates for both server and client).
116. Rendering the template in the browser
<script type="text/javascript" src="twig.js"></script>
<script type="text/javascript" src="tweet.js"></script>
<script language="javascript" type="text/javascript">
var html = Twig.render(tweet, {
message: "...", author: "...", published_at: "..."
}));
</script>
117. Rendering the template in the browser
<script type="text/javascript" src="twig.js"></script>
<script type="text/javascript" src="tweet.js"></script>
<script language="javascript" type="text/javascript">
var html = Twig.render(tweet, {
message: "...", author: "...", published_at: "..."
}));
</script>
{% twig_js name="tweet" %}
118. «A tale of two twig.js»
twig.js by
Johannes Schmitt
http://github.com/schmittjoh/twig.js
twig.js by
John Roepke
http://github.com/justjohn/twig.js
119. twig.js by John Roepke
• A pure JavaScript implementation of
the Twig PHP templating language.
• The goal is to provide a library that is
compatible with both browsers and
server side node.js.
124. A simple object
$offer = new Offer();
$offer->title = "Lorem Ipsum Dolor Sit Amet";
$offer->description = "Ut enim ad minim veniam ...";
$offer->commission = 20;
125. A simple object
$offer = new Offer();
$offer->title = "Lorem Ipsum Dolor Sit Amet";
$offer->description = "Ut enim ad minim veniam ...";
$offer->commission = 20;
TOP-SECRET
information
126. Templates can show any property
Offer data
----------
Title: {{ offer.title }}
Description: {{ offer.description }}
Commission: {{ offer.commission }}
127. Twitter Sandbox
• It’s a regular Twig extension.
• Disabled by default.
• It allows to restrict the functions,
filters, tags and object properties
used in the templates.
• It’s based on security policies.
128. Define a new security policy
$loader = new Twig_Loader_Filesystem('...');
$twig = new Twig_Environment($loader, array());
$properties = array(
'Offer' => array('title', 'description')
);
$policy = new Twig_Sandbox_SecurityPolicy(
array(), array(), array(), $properties, array()
);
129. Define a new security policy
$loader = new Twig_Loader_Filesystem('...');
commission is
$twig = new Twig_Environment($loader, array());
$properties = array( not included
'Offer' => array('title', 'description')
);
$policy = new Twig_Sandbox_SecurityPolicy(
array(), array(), array(), $properties, array()
);
130. Define a new security policy
$loader = new Twig_Loader_Filesystem('...');
$twig = new Twig_Environment($loader, array());
$properties = array('Offer' => array('title', 'description'));
$policy = new Twig_Sandbox_SecurityPolicy(
array(), array(), array(), $properties, array()
);
$sandbox = new Twig_Extension_Sandbox(
$policy, true
);
$twig->addExtension($sandbox);
131. Define a new security policy
$loader = new Twig_Loader_Filesystem('...');
$twig = new Twig_Environment($loader, array());
$properties = array('Offer' => array('title', 'description'));
$policy = new Twig_Sandbox_SecurityPolicy(
array(), array(), array(), $properties, array()
);
$sandbox = new Twig_Extension_Sandbox(
$policy, true
); ALL templates are sandboxed
$twig->addExtension($sandbox);
132. The template now displays an error
Offer data
----------
Title: {{ offer.title }}
Description: {{ offer.description }}
Commission: {{ offer.commission }}
133. The template now displays an error
Offer data
----------
Title: {{ offer.title }}
Description: {{ offer.description }}
Whoops, looks like something went wrong.
Commission: {{ offer.commission }}
Calling "comission" property on a "Offer"
object is not allowed in ... at line 6.
135. Allow to use just 3 filters
$policy = new Twig_Sandbox_SecurityPolicy(
$tags,
array('escape', 'upper', 'lower'),
$methods,
$properties,
$functions
);
136. Allow to use just 2 tags
$policy = new Twig_Sandbox_SecurityPolicy(
array('include', 'extends'),
$filters,
$methods,
$properties,
$functions
);
137. Use any tag except include and extends
$policy = new Twig_Sandbox_SecurityPolicy(
array_diff(
array_keys($twig->getTags()),
array('include', 'extends')
),
$filters,
$methods,
$properties,
$functions
);
146. Use a different base template
$loader = new Twig_Loader_Filesystem('...');
$twig = new Twig_Environment($loader, array(
'base_template_class' => 'ACMEMyTwigTemplate',
));
# if you use Symfony2
# app/config/config.yml
twig:
base_template_class: "ACMEMyTwigTemplate"
147. The new base template class
class MyTwigTemplate extends Twig_Template
{
public function render(array $context)
{
$trace = ...
return str_replace(
"</body>",
$trace."n</body>",
parent::render($context)
);
}
}
148. The new base template class
abstract class MyTwigTemplate extends Twig_Template
{
public function render(array $context)
{
$trace = sprintf('<span data-host="%s" data-elapsed="%s sec."
data-timestamp="%s"></span>',
php_uname(),
microtime(true) - $_SERVER['REQUEST_TIME'],
microtime(true)
);
return str_replace("</body>", $trace."n</body>", parent::render($context));
}
149. Adding a trace to all web pages
<html>
<head> ... </head>
<body>
...
<span data-host="Darwin 10.8.0 ..."
data-elapsed="0.97804594039 sec."
data-timestamp="1339609672.9781">
</span>
</body>
</html>
154. The use_strict_variables option
$loader = new Twig_Loader_Filesystem('...');
$twig = new Twig_Environment($loader, array(
'use_strict_variables’ => false
));
155. The use_strict_variables option
$loader = new Twig_Loader_Filesystem('...');
$twig = new Twig_Environment($loader, array(
'use_strict_variables’ => false
));
inexistent variables won’t
produce a Twig error page
158. Use fallbacks and ignore errors
{% include [
'layout_' ~ locale ~ '.html.twig',
'layout.html.twig'
] ignore missing %}
159. Twig linter
• A linter detects syntax errors
automatically.
• Use it as a preventive measure to
detect errors before serving pages to
users.
160. Twig linter in practice
$twig = new Twig_Environment($loader, array(..));
try {
$twig->parse($twig->tokenize($plantilla));
echo "[OK]";
} catch (Twig_Error_Syntax $e) {
echo "[ERROR] There are some syntax errors";
}