TWIG
            tips & tricks
SUNSHINEPHP             JAVIER EGUILUZ
FEBRUARY 8TH 2013
Thanks to sponsors and organizers




      Adam            Pablo
      Culp            Godel
About me


           Javier Eguiluz
           I’m a programmer
           and trainer from Spain.
I
SYMFONY
I’m a long-term Symfony enthusiast
My Symfony2 book


              Agile web development
              with Symfony2
My Symfony website




                          WINNER 2011
                     Best 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             Jinja2
                    (PHP, Symfony)   (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              WRONG
{% set content = include('index.twig') %}

                                OK
{% set content %}
{{ include('index.twig') }}
{% endset %}
«include» function                      WRONG
{{ include('index.twig')|striptags('<a>')[0:80] }}

{% set content %}                       OK
{{ include('index.twig') }}
{% endset %}

{{ content|striptags('<a>')[0:80] }}
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') }}
                                         equivalent

{{ 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') }}
                                         equivalent

{{ text|convert_encoding(
        from='ISO-8859-1', to='UTF-8') }}

              argument order changed!
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
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 %}
How is the
                   array sorted?


{% for i in array|sort %}
  {# ... #}
{% endfor %}
PHP defines 15 sorting functions
•   asort()      •   array_multisort()
•   arsort()     •   natcasesort()
•   krsort()     •   natsort()
•   ksort()      •   rsort()
•   rsort()      •   shuffle()
•   shuffle()    •   uasort()
•   sort()       •   uksort()
•   usort()
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.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'),
       // ...
    );
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'),
       // ...
    );
«sort» filter uses «asort» function
new 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»
// asort
A1, a0, a10, a2

// natcasesort
a0, A1, a2, a10
1. Define a new Twig extension
class MyCoreExtension
      extends Twig_Extension_Core {
   // ...
}
2. Define the new «sort» filter
class MyCoreExtension extends Twig_Extension_Core {
    public function getFilters() {
      // ...
    }
}
2. Define the new «sort» filter
class MyCoreExtension extends Twig_Extension_Core {
   public function getFilters() {
        return array_merge(
           parent::getFilters(),
           array( ... )
        );
    }
}
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;
    }
}
3. Register the new extension
$twig = new Twig_Environment( ... );

$twig->addExtension(
   new MyCoreExtension()
);
This is now
                   natcasesort


{% for i in array|sort %}
  {# ... #}
{% endfor %}
DYNAMIC
FUNCTIONS
WordPress template tags
the_ID()
the_title()
the_time()
the_content()
the_category()
the_shortlink()
WordPress template tags
the_ID()          class Post {
                     $id = ...
the_title()          $title = ...
the_time()           $time = ...
the_content()        $content = ...
                     $category = ...
the_category()       $shortlink = ...
the_shortlink()      // ...
                  }
the_*()
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
$twig->addFunction(
  'the_*',
   new Twig_Function_Function('wordpress')
);          don’t use regexps,
            just asterisks
function wordpress($property, $options)
{
   // ...
}
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() }}


Reference
http://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 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
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
1. Create a new token parser
class 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» 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());
    }
}
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());
    }
}
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
         -> // ...                                                                     this is
         ->write('echo file_get_contents(')                                            very hard
         ->subcompile($this->getNode('file'))
         ->raw(');')
      ;
    }
}
The compiled PHP template
// line 3
echo file_get_contents("simple.twig");
// ...
                  {% source 'simple.twig' %}
// line 5
echo file_get_contents("../../../composer.json");
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,went wrong.
        Whoops, looks like something array());

// ... 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');
}                     path is added before
                      any other path
$twig = new Twig_Environment($loader, array());

// ...
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
    Whoops, looks like something went wrong.
           frontend/index.twig
    Twig_Error_Loader: The "admin/index.twig"

        admin/
    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');
                                     logical path
                     physical 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.js = Twig inside JavaScript
TWIG
       TWIG
Twig.js = Twig inside JavaScript
TWIG
       TWIG
HTML                      HTML
Twig.js = Twig inside JavaScript
TWIG
       TWIG
HTML                      HTML
          JS

JS
Twig.js = Twig inside JavaScript
TWIG
       TWIG
HTML                      HTML
          JS

JS            TWIG


TWIG
«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
«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
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
                              important!
{# tweet.twig #}
{% twig_js name="tweet" %}
<p>
  {{ message }} <span>by {{ author }}</span>
  <time datetime="{{ published_at|date }}">
     {{ published_at|date }}
  </time>
</p>
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
         http://github.com/schmittjoh/twig.js


         twig.js by
         John Roepke
         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 #}         not necessary
{% twig_js name="tweet" %}
<p>
  {{ message }} <span>by {{ author }}</span>
  <time datetime="{{ published_at|date }}">
     {{ published_at|date }}
  </time>
</p>
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>'           template’s
  });                                       source code
  var html = template.render({
     message: "...", author: "...", published_at: "..."
  });
</script>
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('...');

                                                  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()
);
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
);                ALL templates are sandboxed
$twig->addExtension($sandbox);
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 }}
      Whoops, looks like something went wrong.
Commission: {{ offer.commission }}
     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' => 'ACMEMyTwigTemplate',
));

# if you use Symfony2
# app/config/config.yml
twig:
  base_template_class: "ACMEMyTwigTemplate"
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)
        );
    }
}
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));
  }
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
reuse a fixed
  structure

extends + include = embed
reuse a fixed   reuse
  structure       contents

extends + include = embed
reuse a fixed    reuse
  structure        contents

extends + include = embed
              reuse contents and a
                 flexible structure
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':Magic
                                    Twig   'span9',
                                    in progress...
                             'col2_span': 'span3' } %}

  {% block column1 %} {# ... #} {% endblock %}

  {% block column2 %} {# ... #} {% endblock %}

{% endembed %}
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   • Sandbox
• Overriding filters   • Base template
• Dynamic functions    • Defensive design
• Custom tags          • Embed tag
• Template loaders
• Namespaces
• Twig.js
http://twig.sensiolabs.org
THANK YOU.
CONTACT ME
Contact me
• javier.eguiluz@gmail.com
• linkedin.com/in/javiereguiluz
• twitter.com/javiereguiluz
• github.com/javiereguiluz

Twig tips and tricks

  • 1.
    TWIG tips & tricks SUNSHINEPHP JAVIER EGUILUZ FEBRUARY 8TH 2013
  • 2.
    Thanks to sponsorsand organizers Adam Pablo Culp Godel
  • 3.
    About me Javier Eguiluz I’m a programmer and trainer from Spain.
  • 4.
  • 5.
    I’m a long-termSymfony enthusiast
  • 6.
    My Symfony2 book Agile web development with Symfony2
  • 7.
    My Symfony website WINNER 2011 Best Symfony Blog
  • 8.
    I’m the «A week of Symfony» guy
  • 9.
    I’m the «A week of Symfony» guy this is me!
  • 10.
  • 11.
    Twig is... • Thebest 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
  • 12.
  • 13.
    Agenda • Tips andtricks about Twig. • Advanced features. • Defensive template design. • Best practices. • New and noteworthy Twig features.
  • 14.
  • 15.
    Twig development activityis crazy! (last 12 months) Twig Jinja2 (PHP, Symfony) (Python, Django) Commits 525 11 Authors 35 5 Versions released 23 0
  • 16.
  • 17.
    «template_from_string» function {% setuser_template = "{{ description[0:80] }} <br/> Price: {{ product.price }}" %} {% include template_from_string(user_template) %}
  • 18.
  • 19.
    «include» function {% include'template.twig' %} {{ include('template.twig') }} equivalent
  • 20.
    «include» function WRONG {% set content = include('index.twig') %} OK {% set content %} {{ include('index.twig') }} {% endset %}
  • 21.
    «include» function WRONG {{ include('index.twig')|striptags('<a>')[0:80] }} {% set content %} OK {{ include('index.twig') }} {% endset %} {{ content|striptags('<a>')[0:80] }}
  • 22.
  • 23.
    «first» and «last»filters {% set firstElement = array|first %} {% set lastElement = array|last %}
  • 24.
    «first» and «last»filters {% set first = array[0] %} {% set last = array[array|length - 1] %}
  • 25.
    «first» and «last»filters {% set first = array[0] %} {% set last = array[array|length - 1] %} only works for zero- indexed numeric arrays
  • 26.
    «first» and «last»filters {{ [1, 2, 3, 4]|first }} 1 {{ { a: 1, b: 2, c: 3, d: 4 }|first }} 1 {{ '1234'|first }} 1 result
  • 27.
  • 28.
    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') }}
  • 29.
    Named arguments {{ text|convert_encoding('UTF-8','ISO-8859-1') }} {{ text|convert_encoding( to='UTF-8', from='ISO-8859-1') }} equivalent {{ text|convert_encoding( from='ISO-8859-1', to='UTF-8') }}
  • 30.
    Named arguments {{ text|convert_encoding('UTF-8','ISO-8859-1') }} {{ text|convert_encoding( to='UTF-8', from='ISO-8859-1') }} equivalent {{ text|convert_encoding( from='ISO-8859-1', to='UTF-8') }} argument order changed!
  • 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
  • 32.
  • 33.
    Functions and filtersbefore 1.12 $twig->addFunction('functionName', new Twig_Function_Function('someFunction') ); $twig->addFunction('otherFunction', new Twig_Function_Method($this, 'someMethod') );
  • 34.
    Functions and filtersin 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() { ... } ));
  • 35.
    Functions and filtersin 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() { ... } ));
  • 36.
    Functions and filtersin 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
  • 37.
    OVERRIDING FILTERS
  • 38.
  • 39.
    {% for iin array|sort %} {# ... #} {% endfor %}
  • 40.
    How is the array sorted? {% for i in array|sort %} {# ... #} {% endfor %}
  • 41.
    PHP defines 15sorting functions • asort() • array_multisort() • arsort() • natcasesort() • krsort() • natsort() • ksort() • rsort() • rsort() • shuffle() • shuffle() • uasort() • sort() • uksort() • usort()
  • 42.
    Overriding filters • Wherecan I find the PHP function used by Twig? • How can I override it with my own implementation?
  • 43.
    Where Twig defineseverything 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 defineseverything 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 ✔ Wherecan I find the PHP function • used by Twig? • How can I override it with my own implementation?
  • 47.
    Replace «asort» with«natcasesort» // asort A1, a0, a10, a2 // natcasesort a0, A1, a2, a10
  • 48.
    1. Define anew Twig extension class MyCoreExtension extends Twig_Extension_Core { // ... }
  • 49.
    2. Define thenew «sort» filter class MyCoreExtension extends Twig_Extension_Core { public function getFilters() { // ... } }
  • 50.
    2. Define thenew «sort» filter class MyCoreExtension extends Twig_Extension_Core { public function getFilters() { return array_merge( parent::getFilters(), array( ... ) ); } }
  • 51.
    2. Define thenew «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 thenew extension $twig = new Twig_Environment( ... ); $twig->addExtension( new MyCoreExtension() );
  • 53.
    This is now natcasesort {% for i in array|sort %} {# ... #} {% endfor %}
  • 54.
  • 55.
  • 56.
    WordPress template tags the_ID() class Post { $id = ... the_title() $title = ... the_time() $time = ... the_content() $content = ... $category = ... the_category() $shortlink = ... the_shortlink() // ... }
  • 57.
  • 58.
    Variable functions $twig->addFunction( 'the_*', new Twig_Function_Function('wordpress') ); function wordpress($property, $options) { // ... }
  • 59.
    Variable functions $twig->addFunction( 'the_*', new Twig_Function_Function('wordpress') ); function wordpress($property, $options) { // ... }
  • 60.
    Variable functions $twig->addFunction( 'the_*', new Twig_Function_Function('wordpress') ); don’t use regexps, just asterisks function wordpress($property, $options) { // ... }
  • 61.
    Variable functions inpractice {{ the_ID() }} function wordpress('ID') { ... } {{ the_content() }} function wordpress('content') { ... }
  • 62.
    Variable functions inpractice {{ the_title('<h3>', '</h3>') }} function wordpress( 'title', array('<h3>', '</h3>') ) { ... }
  • 63.
  • 64.
    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()
  • 65.
    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()
  • 66.
  • 67.
  • 68.
    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') ));
  • 69.
    Exposing PHP functions {{php_print_r(['value1', 'value2']) }} {{ php_crypt('mypassword') }} {{ php_memory_get_usage() }} {{ php_uniqid() }} Reference http://github.com/lidaa/LidaaTwigBundle
  • 70.
  • 71.
  • 72.
    {% source ‘...’%} file_get_contents()
  • 73.
    The new «source»tag {% source 'home.twig' %} {% source '../../../composer.json' %}
  • 74.
    How does Twigwork 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 Twigwork 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 anew token parser class SourceTokenParser extends Twig_TokenParser { public function getTag() { return 'source'; } }
  • 77.
    2. Register thenew token parser $loader = new Twig_Loader_Filesystem(...); $twig = new Twig_Environment($loader, array(...)); $twig->addTokenParser( new SourceTokenParser() );
  • 78.
    3. Fill inthe «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 inthe «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 thenode 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 thenode 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 PHPtemplate // line 3 echo file_get_contents("simple.twig"); // ... {% source 'simple.twig' %} // line 5 echo file_get_contents("../../../composer.json");
  • 83.
  • 84.
    Most apps usea 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 atruntime $loader = new Twig_Loader_Filesystem('/templates'); $twig = new Twig_Environment($loader, array()); if ( ... ) { $twig->addLoader( new Twig_Loader_Filesystem('/special_templates') ); } // ...
  • 89.
    Storing templates inseveral folders $loader = new Twig_Loader_Filesystem(array( __DIR__.'/default', __DIR__.'/templates', __DIR__.'/themes' )); $twig = new Twig_Environment($loader, array()); // ...
  • 90.
    Storing templates inseveral 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 atruntime $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()); // ...
  • 94.
    It’s difficult toprioritize folders $loader ->addPath(...) ->addPath(...) ->prependPath(...) ->addPath(...) ->prependPath(...) ;
  • 95.
  • 96.
    Namespaces are betterthan folders templates/ themes/index.twig admin/index.twig frontend/index.twig
  • 97.
    Namespaces are betterthan 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 workif folders change templates/ themes/index.twig frontend/index.twig admin/
  • 99.
    App doesn’t workif 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 usingnamespaces $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 usingnamespaces $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 usingnamespaces $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 usingnamespaces $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 usecase $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');
  • 105.
  • 106.
    Twig.js = Twiginside JavaScript
  • 107.
    Twig.js = Twiginside JavaScript TWIG TWIG
  • 108.
    Twig.js = Twiginside JavaScript TWIG TWIG HTML HTML
  • 109.
    Twig.js = Twiginside JavaScript TWIG TWIG HTML HTML JS JS
  • 110.
    Twig.js = Twiginside JavaScript TWIG TWIG HTML HTML JS JS TWIG TWIG
  • 111.
    «A tale oftwo 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 oftwo 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 JohannesSchmitt • 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).
  • 114.
    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>
  • 115.
    Defining the template important! {# tweet.twig #} {% twig_js name="tweet" %} <p> {{ message }} <span>by {{ author }}</span> <time datetime="{{ published_at|date }}"> {{ published_at|date }} </time> </p>
  • 116.
    Rendering the templatein 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 templatein 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 oftwo 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 JohnRoepke • 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.
  • 120.
    Defining the template {#tweet.twig #} not necessary {% twig_js name="tweet" %} <p> {{ message }} <span>by {{ author }}</span> <time datetime="{{ published_at|date }}"> {{ published_at|date }} </time> </p>
  • 121.
    Rendering the templatein the browser <script type="text/javascript" src="twig.js"></script> <script language="javascript" type="text/javascript"> var template = twig({data: '<p> {{ message }} ... </p>' template’s }); source code var html = template.render({ message: "...", author: "...", published_at: "..." }); </script>
  • 122.
    Rendering the templatein 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>
  • 123.
  • 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 showany property Offer data ---------- Title: {{ offer.title }} Description: {{ offer.description }} Commission: {{ offer.commission }}
  • 127.
    Twitter Sandbox • It’sa 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 newsecurity 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 newsecurity 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 newsecurity 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 newsecurity 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 nowdisplays an error Offer data ---------- Title: {{ offer.title }} Description: {{ offer.description }} Commission: {{ offer.commission }}
  • 133.
    The template nowdisplays 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.
  • 134.
    Security policy arguments $policy= new Twig_Sandbox_SecurityPolicy( $tags, $filters, $methods, $properties, $functions );
  • 135.
    Allow to usejust 3 filters $policy = new Twig_Sandbox_SecurityPolicy( $tags, array('escape', 'upper', 'lower'), $methods, $properties, $functions );
  • 136.
    Allow to usejust 2 tags $policy = new Twig_Sandbox_SecurityPolicy( array('include', 'extends'), $filters, $methods, $properties, $functions );
  • 137.
    Use any tagexcept include and extends $policy = new Twig_Sandbox_SecurityPolicy( array_diff( array_keys($twig->getTags()), array('include', 'extends') ), $filters, $methods, $properties, $functions );
  • 138.
  • 139.
    Adding a traceto 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>
  • 140.
  • 141.
    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) { ... } // ... }
  • 142.
    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) { ... } // ... }
  • 143.
    The base templateclass class __TwigTemplate_09f...2f2 extends Twig_Template { // ... }
  • 144.
    lib/Twig/Template.php abstract class Twig_Template{ public function render(array $context) { // ... } // ... }
  • 145.
    lib/Twig/Template.php abstract class Twig_Template{ public function render(array $context) { tweak this method // ... to tweak templates } // ... }
  • 146.
    Use a differentbase 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 basetemplate 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 basetemplate 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 traceto 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>
  • 150.
    DEFENSIVE DESIGN
  • 151.
    Errors will happen ERROR 500 ERROR
  • 152.
    Errors will happen ERROR 500 ERROR users prefer this
  • 153.
    Default values {{ variable|default("value")}} (discount {{ discount|default(0) }}%) Hi {{ user_name|default("") }}!
  • 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
  • 156.
    Ignore missing templates {%include 'section_' ~ slug ~ '.twig' ignore missing %}
  • 157.
    Define fallback templates {%extends [ 'layout_' ~ locale ~ '.html.twig', 'layout.html.twig' ] %}
  • 158.
    Use fallbacks andignore errors {% include [ 'layout_' ~ locale ~ '.html.twig', 'layout.html.twig' ] ignore missing %}
  • 159.
    Twig linter • Alinter detects syntax errors automatically. • Use it as a preventive measure to detect errors before serving pages to users.
  • 160.
    Twig linter inpractice $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"; }
  • 161.
    INTEGRATING TWITTER BOOTSTRAP
  • 162.
  • 163.
  • 164.
    Each row addsup to 12 columns 3 12 = 3 + 9
  • 165.
    Each row addsup to 12 columns 3 12 = 3 + 4 + 5
  • 166.
    Each row addsup to 12 columns 3 12 = 3 + 2 + 3 + 4
  • 167.
    Most grids arebased on 2 or 3 columns 3 2 columns
  • 168.
    Most grids arebased on 2 or 3 columns 3 2 columns
  • 169.
    Most grids arebased on 2 or 3 columns 3 3 columns
  • 170.
  • 171.
    reuse a fixed structure extends + include = embed
  • 172.
    reuse a fixed reuse structure contents extends + include = embed
  • 173.
    reuse a fixed reuse structure contents extends + include = embed reuse contents and a flexible structure
  • 174.
    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>
  • 175.
    2-column grid inpractice {% embed 'grid_2.twig' with { 'col1_span': 'span9', 'col2_span': 'span3' } %} {% block column1 %} {# ... #} {% endblock %} {% block column2 %} {# ... #} {% endblock %} {% endembed %}
  • 176.
    2-column grid inpractice {% embed 'grid_2.twig' with { 'col1_span':Magic Twig 'span9', in progress... 'col2_span': 'span3' } %} {% block column1 %} {# ... #} {% endblock %} {% block column2 %} {# ... #} {% endblock %} {% endembed %}
  • 177.
    2-column grid inpractice {% embed 'grid_2.twig' with { 'layout': '9_3' } %} {% block column1 %} {# ... #} {% endblock %} {% block column2 %} {# ... #} {% endblock %} {% endembed %}
  • 178.
    2-column grid inpractice {% embed 'grid_2.twig' with { 'layout': '9_3' } %} {% set col1_span = layout|split('_')[0:]|join %} {% set col2_span = layout|split('_')[1:]|join %}
  • 179.
    3-column grid {% embed'grid_3.twig' with { 'layout': '3_6_3' } %} {% block column1 %} {# ... #} {% endblock %} {% block column2 %} {# ... #} {% endblock %} {% block column3 %} {# ... #} {% endblock %} {% endembed %}
  • 180.
    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 %}
  • 181.
    Recap • New andnoteworthy • Sandbox • Overriding filters • Base template • Dynamic functions • Defensive design • Custom tags • Embed tag • Template loaders • Namespaces • Twig.js
  • 182.
  • 183.
  • 184.
  • 185.
    Contact me • javier.eguiluz@gmail.com •linkedin.com/in/javiereguiluz • twitter.com/javiereguiluz • github.com/javiereguiluz