Post on 08-Sep-2014
description
JAVIER EGUILUZSUNSHINEPHPFEBRUARY 8TH 2013
TWIGtips & tricks
Thanks to sponsors and organizers
AdamCulp
PabloGodel
About me
Javier EguiluzI’m a programmerand trainer from Spain.
I SYMFONY
I’m a long-term Symfony enthusiast
My Symfony2 book
Agile web developmentwith Symfony2
My Symfony website
WINNER 2011Best Symfony Blog
I’m the « A week of Symfony» guy
I’m the « A week of Symfony» guy
this is me!
I TWIG
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
AGENDA
Agenda• Tips and tricks about Twig.
• Advanced features.
• Defensive template design.
• Best practices.
• New and noteworthy Twig features.
NEW & NOTEWORTHY
Twig development activity is crazy!
(last 12 months) Twig(PHP, Symfony)
Jinja2(Python, Django)
Commits 525 11
Authors 35 5
Versions released 23 0
New and noteworthy
1 2 3 4 5
«template_from_string» function
{% set user_template = "{{ description[0:80] }}<br/> Price: {{ product.price }}" %}
{% include template_from_string(user_template) %}
New and noteworthy
1 2 3 4 5
«include» function
{% include 'template.twig' %}
{{ include('template.twig') }}equivalent
«include» function
{% set content = include('index.twig') %}
{% set content %}{{ include('index.twig') }}
{% endset %}
WRONG
OK
«include» function{{ include('index.twig')|striptags('<a>')[0:80] }}
{% set content %}{{ include('index.twig') }}
{% endset %}
{{ content|striptags('<a>')[0:80] }}
WRONG
OK
New and noteworthy
1 2 3 4 5
«first» and «last» filters
{% set firstElement = array|first %}
{% set lastElement = array|last %}
«first» and «last» filters
{% set first = array[0] %}{% set last = array[array|length - 1] %}
«first» and «last» filters
{% set first = array[0] %}{% set last = array[array|length - 1] %}
only works for zero-indexed numeric arrays
«first» and «last» filters
{{ [1, 2, 3, 4]|first }} 1
{{ { a: 1, b: 2, c: 3, d: 4 }|first }} 1
{{ '1234'|first }} 1
result
New and noteworthy
1 2 3 4 5
Named arguments{{ text|convert_encoding('UTF-8', 'ISO-8859-1') }}
{{ text|convert_encoding( to='UTF-8', from='ISO-8859-1') }}
{{ text|convert_encoding( from='ISO-8859-1', to='UTF-8') }}
Named arguments{{ text|convert_encoding('UTF-8', 'ISO-8859-1') }}
{{ text|convert_encoding( to='UTF-8', from='ISO-8859-1') }}
{{ text|convert_encoding( from='ISO-8859-1', to='UTF-8') }}
equivalent
Named arguments{{ text|convert_encoding('UTF-8', 'ISO-8859-1') }}
{{ text|convert_encoding( to='UTF-8', from='ISO-8859-1') }}
{{ text|convert_encoding( from='ISO-8859-1', to='UTF-8') }}
equivalent
argument order changed!
Named arguments{{ "now"|date("Y-m-d", "America/New_York") }}
{{ "now"|date(timezone="America/New_York") }}
mandatory to set the second argument
just set the argument you need
New and noteworthy
1 2 3 4 5
Functions and filters before 1.12$twig->addFunction('functionName', new Twig_Function_Function('someFunction'));
$twig->addFunction('otherFunction', new Twig_Function_Method($this, 'someMethod'));
Functions and filters in Twig 1.12$twig->addFunction(new Twig_SimpleFunction( 'twig_function', 'some_php_function'));
$twig->addFunction(new Twig_SimpleFunction( 'twig_function', array($object, 'method_name')));
$twig->addFunction(new Twig_SimpleFunction( 'twig_function', function() { ... }));
Functions and filters in Twig 1.12$twig->addFunction(new Twig_SimpleFunction( 'twig_function', 'some_php_function'));
$twig->addFunction(new Twig_SimpleFunction( 'twig_function', array($object, 'method_name')));
$twig->addFunction(new Twig_SimpleFunction( 'twig_function', function() { ... }));
Functions and filters in Twig 1.12$twig->addFunction(new Twig_SimpleFunction( 'twig_function', 'some_php_function'));
$twig->addFunction(new Twig_SimpleFunction( 'twig_function', array($object, 'method_name')));
$twig->addFunction(new Twig_SimpleFunction( 'twig_function', function() { ... })); super cool
OVERRIDING FILTERS
USE WITH CAUTION
{% for i in array|sort %} {# ... #}
{% endfor %}
{% for i in array|sort %} {# ... #}
{% endfor %}
How is the array sorted?
PHP defines 15 sorting functions• asort()• arsort()• krsort()• ksort()• rsort()• shuffle()• sort()• usort()
• array_multisort()• natcasesort()• natsort()• rsort()• shuffle()• uasort()• uksort()
Overriding filters• Where can I find the PHP function
used by Twig?
• How can I override it with my own implementation?
Where Twig defines everything
lib/twig/Extension/Core.phpclass Twig_Extension_Core extends Twig_Extension { public function getTokenParsers() { return array( new Twig_TokenParser_For(), new Twig_TokenParser_If(), 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'), // ... ); }
+1,300 lines class!
Where Twig defines everything
lib/twig/Extension/Core.phpclass Twig_Extension_Core extends Twig_Extension { public function getTokenParsers() { return array( new Twig_TokenParser_For(), new Twig_TokenParser_If(), 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'), // ... ); }
• Filters• Functions• Tags• Operators• Tests
«sort» filter uses «asort» functionnew Twig_SimpleFilter('sort', 'twig_sort_filter'),
// ...
function twig_sort_filter($array){ asort($array);
return $array;}
Overriding filters• Where can I find the PHP function
used by Twig?
• How can I override it with my own implementation?
✔
Replace «asort» with «natcasesort»
// asortA1, a0, a10, a2
// natcasesorta0, A1, a2, a10
1. Define a new Twig extension
class MyCoreExtension extends Twig_Extension_Core { // ...}
2. Define the new «sort» filterclass MyCoreExtension extends Twig_Extension_Core {
public function getFilters() { // ... }}
2. Define the new «sort» filterclass MyCoreExtension extends Twig_Extension_Core { public function getFilters() {
return array_merge( parent::getFilters(), array( ... ) ); }}
2. Define the new «sort» filterclass 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; }}
3. Register the new extension
$twig = new Twig_Environment( ... );
$twig->addExtension( new MyCoreExtension());
{% for i in array|sort %} {# ... #}
{% endfor %}
This is now natcasesort
DYNAMIC FUNCTIONS
WordPress template tags
the_ID()the_title()the_time()the_content()the_category()the_shortlink()
WordPress template tags
the_ID()the_title()the_time()the_content()the_category()the_shortlink()
class Post { $id = ... $title = ... $time = ... $content = ... $category = ... $shortlink = ... // ...}
the_*()
$twig->addFunction(
'the_*', new Twig_Function_Function('wordpress'));
function wordpress($property, $options){ // ...}
Variable functions
$twig->addFunction(
'the_*', new Twig_Function_Function('wordpress'));
function wordpress($property, $options){ // ...}
Variable functions
$twig->addFunction(
'the_*', new Twig_Function_Function('wordpress'));
function wordpress($property, $options){ // ...}
Variable functions
don’t use regexps,just asterisks
Variable functions in practice
{{ the_ID() }}function wordpress('ID') { ... }
{{ the_content() }}function wordpress('content') { ... }
Variable functions in practice
{{ the_title('<h3>', '</h3>') }}
function wordpress( 'title', array('<h3>', '</h3>')) { ... }
WordPress template tags
next_image_link()next_post_link()next_posts_link()previous_image_link()previous_post_link()previous_posts_link()
WordPress template tags
next_image_link() next_*_link()next_post_link() next_*_link()next_posts_link() next_*_link()previous_image_link() previous_*_link()previous_post_link() previous_*_link()previous_posts_link() previous_*_link()
WordPress template tags
next_image_link() *_*_link()next_post_link() *_*_link()next_posts_link() *_*_link()previous_image_link() *_*_link()previous_post_link() *_*_link()previous_posts_link() *_*_link()
USE WITH CAUTION
php_*()
php_* dynamic function$twig->addFunction(new Twig_SimpleFunction('php_*',
function() { $arg_list = func_get_args(); $function = array_shift($arg_list); return call_user_func_array($function, $arg_list); }, array('pre_escape' => 'html', 'is_safe' => array('html')));
Exposing PHP functions
{{ php_print_r(['value1', 'value2']) }}{{ php_crypt('mypassword') }}{{ php_memory_get_usage() }}{{ php_uniqid() }}
Referencehttp://github.com/lidaa/LidaaTwigBundle
CUSTOM TAGS
{% source ‘...’ %}
{% source ‘...’ %}file_get_contents()
The new «source» tag
{% source 'home.twig' %}
{% source '../../../composer.json' %}
How does Twig work internallyclass __TwigTemplate_06dff1ec7c2cceb3f45ac76fc059b730 extends Twig_Template{ public function __construct(Twig_Environment $env) { parent::__construct($env);
$this->parent = $this->env->loadTemplate("layout.twig");
$this->blocks = array(
PHP file
Lexer Parser Compiler
Twigtemplate
{% source
'simple.twig' %}
{# ... #}
How does Twig work internallyclass __TwigTemplate_06dff1ec7c2cceb3f45ac76fc059b730 extends Twig_Template{ public function __construct(Twig_Environment $env) { parent::__construct($env);
$this->parent = $this->env->loadTemplate("layout.twig");
$this->blocks = array(
PHP file
Lexer Parser Compiler
Twigtemplate
{% source
'simple.twig' %}
{# ... #}
you must provide these
1. Create a new token parserclass SourceTokenParser extends Twig_TokenParser{ public function getTag() { return 'source'; }}
2. Register the new token parser$loader = new Twig_Loader_Filesystem(...);$twig = new Twig_Environment($loader, array(...));
$twig->addTokenParser( new SourceTokenParser());
3. Fill in the «parse» methodclass 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()); }}
3. Fill in the «parse» methodclass 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()); }}
this is hard
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(');') ; }}
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(');') ; }}
this is very hard
The compiled PHP template// line 3echo file_get_contents("simple.twig");// ...
// line 5echo file_get_contents("../../../composer.json");
{% source 'simple.twig' %}
TEMPLATE LOADERS
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');
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());
// ...
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));
// ...
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!
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') );}
// ...
Storing templates in several folders
$loader = new Twig_Loader_Filesystem(array( __DIR__.'/default', __DIR__.'/templates', __DIR__.'/themes'));$twig = new Twig_Environment($loader, array());
// ...
Storing templates in several folders
$loader = new Twig_Loader_Filesystem(array( __DIR__.'/default', __DIR__.'/templates', __DIR__.'/themes'));$twig = new Twig_Environment($loader, array());
// ...
Whoops, looks like something went wrong.
Twig_Error_Loader: The "_DIR_/templates" directory does not exist.
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());
// ...
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());
// ...
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());
// ...
path is added before any other path
It’s difficult to prioritize folders
$loader ->addPath(...) ->addPath(...) ->prependPath(...) ->addPath(...) ->prependPath(...);
NAMESPACES
Namespaces are better than folders
templates/
themes/index.twig
admin/index.twig
frontend/index.twig
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');
App doesn’t work if folders change
templates/
themes/index.twig
frontend/index.twig
admin/
App doesn’t work if folders change
templates/
themes/index.twig
frontend/index.twig
admin/
Whoops, looks like something went wrong.
Twig_Error_Loader: The "admin/index.twig" template does not exist.
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');
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');
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');
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');
physical pathlogical path
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');
TWIG.JS
Twig.js = Twig inside JavaScript
TWIG
Twig.js = Twig inside JavaScriptTWIG
TWIG
Twig.js = Twig inside JavaScriptTWIG
HTML HTML
TWIG
Twig.js = Twig inside JavaScriptTWIG
HTML
JS
HTML
JS
TWIG
Twig.js = Twig inside JavaScriptTWIG
HTML
JS
TWIG
HTML
JS
TWIG
«A tale of two twig.js»
twig.js by
Johannes Schmitt
twig.js by
John Roepke
http://github.com/schmittjoh/twig.js
https://github.com/justjohn/twig.js
«A tale of two twig.js»
twig.js by
Johannes Schmitt
twig.js by
John Roepke
http://github.com/schmittjoh/twig.js
https://github.com/justjohn/twig.js
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).
Defining the template{# tweet.twig #}
{% twig_js name="tweet" %}<p> {{ message }} <span>by {{ author }}</span> <time datetime="{{ published_at|date }}"> {{ published_at|date }} </time></p>
Defining the template{# tweet.twig #}
{% twig_js name="tweet" %}<p> {{ message }} <span>by {{ author }}</span> <time datetime="{{ published_at|date }}"> {{ published_at|date }} </time></p>
important!
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>
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" %}
«A tale of two twig.js»
twig.js by
Johannes Schmitt
twig.js by
John Roepke
http://github.com/schmittjoh/twig.js
http://github.com/justjohn/twig.js
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.
Defining the template{# tweet.twig #}
{% twig_js name="tweet" %}<p> {{ message }} <span>by {{ author }}</span> <time datetime="{{ published_at|date }}"> {{ published_at|date }} </time></p>
not necessary
Rendering the template in the browser
<script type="text/javascript" src="twig.js"></script>
<script language="javascript" type="text/javascript">
var template = twig({data: '<p> {{ message }} ... </p>' });
var html = template.render({ message: "...", author: "...", published_at: "..." });
</script>
template’s source code
Rendering the template in node.js
<script type="text/javascript">var Twig = require("twig"), express = require('express'), app = express();
app.set("twig options", { strict_variables: false });
app.get('/tweet', function(req, res){ res.render('tweet.twig', { message: "...", author: "...", published_at: "..." });});
app.listen(80);</script>
SANDBOX
A simple object$offer = new Offer();$offer->title = "Lorem Ipsum Dolor Sit Amet";$offer->description = "Ut enim ad minim veniam ...";$offer->commission = 20;
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
Templates can show any property
Offer data----------Title: {{ offer.title }}Description: {{ offer.description }}Commission: {{ offer.commission }}
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.
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());
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());
commission is not included
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);
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);
ALL templates are sandboxed
The template now displays an error
Offer data----------Title: {{ offer.title }}Description: {{ offer.description }}Commission: {{ offer.commission }}
The template now displays an error
Offer data----------Title: {{ offer.title }}Description: {{ offer.description }}Commission: {{ offer.commission }}
Whoops, looks like something went wrong.
Calling "comission" property on a "Offer" object is not allowed in ... at line 6.
Security policy arguments$policy = new Twig_Sandbox_SecurityPolicy(
$tags, $filters, $methods, $properties, $functions);
Allow to use just 3 filters$policy = new Twig_Sandbox_SecurityPolicy(
$tags, array('escape', 'upper', 'lower'), $methods, $properties, $functions);
Allow to use just 2 tags$policy = new Twig_Sandbox_SecurityPolicy(
array('include', 'extends'), $filters, $methods, $properties, $functions);
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);
THE BASE TEMPLATE
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>
Twig cache
cache/09/fc/2d8a188dda8245d295e6324582f2.php
/* homepage.html.twig */
class __TwigTemplate_09f8a...582f2 extends Twig_Template {
public function __construct(Twig_Environment $env) { ... }
protected function doGetParent(array $context) { ... }
protected function doDisplay(array $context, array $blocks) { ... }
// ...}
cache/09/fc/2d8a188dda8245d295e6324582f2.php
/* homepage.html.twig */
class __TwigTemplate_09f8a...582f2 extends Twig_Template {
public function __construct(Twig_Environment $env) { ... }
protected function doGetParent(array $context) { ... }
protected function doDisplay(array $context, array $blocks) { ... }
// ...}
The base template class
class __TwigTemplate_09f...2f2 extends Twig_Template{ // ...}
lib/Twig/Template.php
abstract class Twig_Template { public function render(array $context) { // ... }
// ...
}
lib/Twig/Template.php
abstract class Twig_Template { public function render(array $context) { // ... }
// ...
}
tweak this method to tweak templates
Use a different base template$loader = new Twig_Loader_Filesystem('...');$twig = new Twig_Environment($loader, array( 'base_template_class' => '\ACME\MyTwigTemplate',));
# if you use Symfony2# app/config/config.yml
twig: base_template_class: "\ACME\MyTwigTemplate"
The new base template classclass MyTwigTemplate extends Twig_Template{
public function render(array $context) { $trace = ...
return str_replace( "</body>", $trace."\n</body>",
parent::render($context) ); }}
The new base template classabstract 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));
}
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>
DEFENSIVE DESIGN
Errors will happen
ERROR 500
ERROR
Errors will happen
ERROR 500
ERROR
users prefer this
Default values
{{ variable|default("value") }}
(discount {{ discount|default(0) }}%)Hi {{ user_name|default("") }}!
The use_strict_variables option$loader = new Twig_Loader_Filesystem('...');$twig = new Twig_Environment($loader, array(
'use_strict_variables’ => false));
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
Ignore missing templates
{% include 'section_' ~ slug ~ '.twig' ignore missing %}
Define fallback templates
{% extends [ 'layout_' ~ locale ~ '.html.twig', 'layout.html.twig'
] %}
Use fallbacks and ignore errors
{% include [ 'layout_' ~ locale ~ '.html.twig', 'layout.html.twig'
] ignore missing %}
Twig linter• A linter detects syntax errors
automatically.
• Use it as a preventive measure to detect errors before serving pages to users.
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";}
INTEGRATING TWITTER
BOOTSTRAP
Twitter Bootstrap
Twitter Bootstrap grid model
3
Each row adds up to 12 columns
3
12 = 3 + 9
Each row adds up to 12 columns
3
12 = 3 + 4 + 5
Each row adds up to 12 columns
3
12 = 3 + 2 + 3 + 4
Most grids are based on 2 or 3 columns
3
2 columns
Most grids are based on 2 or 3 columns
3
2 columns
Most grids are based on 2 or 3 columns
3
3 columns
extends + include = embed
extends + include = embed
reuse a fixed structure
extends + include = embed
reuse a fixed structure
reuse contents
extends + include = embed
reuse a fixed structure
reuse contents and a flexible structure
reuse contents
Reusable 2-column grid{# grid_2.twig #}<div class="row"> <div class="{{ col1_span }} {{ col1_offset }}">
{% block column1 %}{% endblock %} </div>
<div class="{{ col2_span }} {{ col2_offset }}">
{% block column2 %}{% endblock %} </div></div>
2-column grid in practice
{% embed 'grid_2.twig' with { 'col1_span': 'span9',
'col2_span': 'span3' } %}
{% block column1 %} {# ... #} {% endblock %}
{% block column2 %} {# ... #} {% endblock %}
{% endembed %}
2-column grid in practice
{% embed 'grid_2.twig' with { 'col1_span': 'span9',
'col2_span': 'span3' } %}
{% block column1 %} {# ... #} {% endblock %}
{% block column2 %} {# ... #} {% endblock %}
{% endembed %}
Twig Magic in progress...
2-column grid in practice
{% embed 'grid_2.twig' with { 'layout': '9_3' } %}
{% block column1 %} {# ... #} {% endblock %}
{% block column2 %} {# ... #} {% endblock %}
{% endembed %}
2-column grid in practice
{% embed 'grid_2.twig' with { 'layout': '9_3' } %}
{% set col1_span = layout|split('_')[0:]|join %}
{% set col2_span = layout|split('_')[1:]|join %}
3-column grid
{% embed 'grid_3.twig' with { 'layout': '3_6_3' } %}
{% block column1 %} {# ... #} {% endblock %}
{% block column2 %} {# ... #} {% endblock %}
{% block column3 %} {# ... #} {% endblock %}
{% endembed %}
3-column grid
{% embed 'grid_3.twig' with { 'layout': '3_6_3' } %}
{% set col1_span = layout|split('_')[0:]|join %}
{% set col2_span = layout|split('_')[1:]|join %}
{% set col3_span = layout|split('_')[2:]|join %}
Recap• New and noteworthy
• Overriding filters
• Dynamic functions
• Custom tags
• Template loaders
• Namespaces
• Twig.js
• Sandbox
• Base template
• Defensive design
• Embed tag
THANK YOU.
CONTACT ME
Contact me• javier.eguiluz@gmail.com
• linkedin.com/in/javiereguiluz
• twitter.com/javiereguiluz
• github.com/javiereguiluz