More Related Content More from Javier Eguiluz (20) Twig, los mejores trucos y técnicas avanzadas1. Twig, los mejores trucos y
técnicas avanzadas
Javier Eguíluz
Universitat Jaume I · Castellón · 15-16 junio 2012 · desymfony.com
2. ¡muchas gracias a nuestros patrocinadores!
PATROCINADOR
PLATINO
PATROCINADORES
ORO
PATROCINADORES
PLATA
PATROCINADORES
BRONCE
4. Código
El código fuente de los ejemplos
más avanzados de esta ponencia
está disponible en:
https://github.com/javiereguiluz/
desymfony!twig
6. <div>
{{ form_label(form.nombre) }}
{{ form_errors(form.nombre) }}
{{ form_widget(form.nombre) }}
</div>
7. {% extends '::base.html.twig' %}
{% form_theme form _self %}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('field_widget') }}
</div>
{% endblock %}
{% block content %}
{# render the form #}
{{ form_row(form.age) }}
{% endblock %}
8. {% extends '::base.html.twig' %}
{% form_theme form _self %}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('field_widget') }}
</div>
{% endblock %}
{% block content %}
{# render the form #}
{{ form_row(form.age) }}
{% endblock %}
9. {% extends '::base.html.twig' %}
{% form_theme form _self %}
{% block integer_widget %}
<div class="integer_widget">
{% set type = type|default('number') %}
{{ block('field_widget') }}
</div>
{% endblock %}
{% block content %}
{# render the form #}
{{ form_row(form.age) }}
{% endblock %}
10. {% form_theme form _self %}
{% block _formulario_nombre_widget %}
<div class="especial"><strong>
{{ block('field_widget') }}
</strong></div>
{% endblock %}
{{ form_widget(form.nombre) }}
11. {% form_theme form _self %}
{% block _formulario_nombre_widget %}
<div class="especial"><strong>
{{ block('field_widget') }}
</strong></div>
{% endblock %}
{{ form_widget(form.nombre) }}
12. class UsuarioType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('nombre')
->add('apellidos')
->add('email')
->...
->add('ciudad')
;
}
public function getName()
{
return 'cupon_backendbundle_usuariotype';
}
}
13. class UsuarioType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('nombre')
->add('apellidos')
->add('email')
->...
->add('ciudad')
;
}
public function getName()
{
return 'cupon_backendbundle_usuariotype';
}
}
21. namespace SymfonyComponentHttpKernel;
abstract class Kernel implements KernelInterface
{
// ...
const VERSION = '2.1.0-DEV';
const VERSION_ID = '20100';
const MAJOR_VERSION = '2';
const MINOR_VERSION = '1';
const RELEASE_VERSION = '0';
const EXTRA_VERSION = 'DEV';
}
22. REDEFINE LOS
FILTROS POR
DEFECTO
1.* BÁSICO INTERMEDIO AVANZADO
24. asort() array_multisort()
arsort() natcasesort()
krsort() natsort()
ksort() rsort()
rsort() shuffle()
shuffle() uasort()
sort() uksort()
usort()
26. lib/twig/Extesion/Core.php
class 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'),
27. {{ array|sort }}
/**
* Sorts an array.
*
* @param array $array An array
*/
function twig_sort_filter($array)
{
asort($array);
return $array;
}
31. class MiCoreExtension extends Twig_Extension_Core {
public function getFilters() {
return array_merge(
parent::getFilters(),
array( ... )
);
}
}
32. $twig = new Twig_Environment($loader, array( ... ));
$twig->addExtension(new MiCoreExtension());
33. class MiCoreExtension 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;
}
}
35. TODO SON
EXPRESIONES
1.5 BÁSICO INTERMEDIO AVANZADO
37. {% set ofertas = {
('oferta' ~ oferta.id): '...',
(3 ~ '_' ~ oferta.nombre|length): '...',
(2**2+1): '...'
} %}
38. {% set ofertas = {
('oferta' ~ oferta.id): '...',
(3 ~ '_' ~ oferta.nombre|length): '...',
(2**2+1): '...'
} %}
48. ESCAPING
AUTOMÁTICO
1.8 BÁSICO INTERMEDIO AVANZADO
49. {% filter escape('js') %}
<script type="text/javascript">
var texto = "<p>Lorem ipsum dolor sit amet</p>";
alert(texto);
</script>
{% endfilter %}
{% filter escape('html') %}
<script type="text/javascript">
var texto = "<p>Lorem ipsum dolor sit amet</p>";
alert(texto);
</script>
{% endfilter %}
50. x3cscript typex3dx22textx2fjavascriptx22x3ex0a
var texto x3d x22x3cpx3eLorem ipsum dolor sit amet
x3cx2fpx3ex22x3bx0a alertx28textox29x3bx0a
x3cx2fscriptx3ex0a
<script type="text/javascript">
var texto = "<p>Lorem ipsum dolor sit
amet</p>";
alert(texto);
</script>
51. {{ variable|e }}
{{ variable|e('html') }}
{{ variable|escape('js') }}
{{ variable|e('js') }}
{% autoescape %} ........... {% endautoescape %}
{% autoescape 'html' %} ... {% endautoescape %}
{% autoescape 'js' %} ....... {% endautoescape %}
{% autoescape false %} .... {% endautoescape %}
52. $twig = new Twig_Environment($loader, array(
'autoescape' => function($nombre_plantilla) {
return decide_escape($nombre_plantilla);
}
));
53. $twig = new Twig_Environment($loader, array(
'autoescape' => function($nombre_plantilla) {
return decide_escape($nombre_plantilla);
}
));
function decide_escape($plantilla) {
$extension = substr($plantilla, strrpos($plantilla, '.') + 1);
switch ($extension) {
'js':
return 'js';
default:
return 'html';
}
}
58. {{ random() }}
{{ random(10) }}
{{ random("abcde") }}
{{ random(['a', 'b', 'c']) }}
59. FUNCIONES
DINÁMICAS
1.5 BÁSICO INTERMEDIO AVANZADO
63. $twig->addFunction(
'the_*',
new Twig_Function_Function('wordpress')
);
function wordpress($funcion, $opciones)
{
// ...
}
64. $twig->addFunction(
'the_*',
new Twig_Function_Function('wordpress')
);
function wordpress($funcion, $opciones)
{
// ...
}
68. {{ the_ID() }}
function wordpress('ID', array()) { ... }
{{ the_content() }}
function wordpress('content', array())
{ ... }
72. FILTROS
DINÁMICOS
1.5 BÁSICO INTERMEDIO AVANZADO
74. $twig->addFilter(
'fecha_*',
new Twig_Filter_Function('fecha')
);
function fecha($funcion, $opciones)
{
// ...
}
75. VARIABLES
GLOBALES
1.* BÁSICO INTERMEDIO AVANZADO
76. {{ app.security }}
{{ app.user }}
{{ app.request }}
{{ app.session }}
{{ app.environment }}
{{ app.debug }}
80. {{ _context }}
{% for i in _context|keys %}
{{ i }} app
assetic
{% endfor %} _parent
oferta
ciudad_por_defecto
ciudadSeleccionada
expirada
...
81. {{ _context }}
{% for i in _context|keys %}
{{ i }} app
assetic
{% endfor %} _parent
oferta
ciudad_por_defecto
ciudadSeleccionada
expirada
...
85. {{ _self }}
{{ _self.getTemplateName }}
OfertaBundle:Default:includes/
oferta.html.twig
86. {{ _self }}
{{ _self.blocks|keys|join(", ") }}
title, stylesheets, rss, javascripts,
id, body
87. {{ _self }}
{{ _self.blocks|keys|join(", ") }}
title, stylesheets, rss, javascripts,
id, body
SÓLO SI NO HAY
OTRO REMEDIO
91. {{ 'now'|date("d/m/Y H:i:s") }}
{{ 'now'|date("d/m/Y H:i:s",
"America/Argentina/Buenos_Aires") }}
{{ 'now'|date("d/m/Y H:i:s", "America/Havana") }}
{{ 'now'|date("d/m/Y H:i:s", "America/Caracas") }}
{{ 'now'|date("d/m/Y H:i:s", "America/Lima") }}
92. España: 11/06/2012 10:45:22
Argentina: 11/06/2012 05:45:22
Cuba: 11/06/2012 04:45:22
Venezuela: 11/06/2012 04:15:22
Perú: 11/06/2012 03:45:22
93. $twig = new Twig_Environment($loader);
$twig->getExtension('core')
->setDateFormat('d-m-Y H:i:s', '%d días');
$twig->getExtension('core')
->setTimezone('America/Montevideo');
99. Tu invitación caduca el
{{ date('+2days')|date }}
{% if date(fechaNacimiento) < date('-18years') %}
¡Eres menor de edad!
{% endif %}
101. OPERADORES
PROPIOS
1.* BÁSICO INTERMEDIO AVANZADO
102. $loader = new Twig_Loader_Filesystem(...);
$twig = new Twig_Environment($loader, array(...));
$twig->addExtension(new OperatorsExtension());
104. class OperatorsExtension extends Twig_Extension
{
public function getName() { return 'OperatorsExtension'; }
public function getOperators()
{
return array(
array(),
array('>>' => array(
'precedence' => 20,
'class' => 'DesymfonyOperatorsMaxOperator',
'associativity' => Twig_ExpressionParser::OPERATOR_LEFT
)
));
}
}
105. class OperatorsExtension extends Twig_Extension
{
public function getName() { return 'OperatorsExtension'; }
public function getOperators()
{
return array(
array(),
array('>>' => array(
'precedence' => 20,
'class' => 'DesymfonyOperatorsMaxOperator',
'associativity' => Twig_ExpressionParser::OPERATOR_LEFT
)
));
}
}
106. class OperatorsExtension extends Twig_Extension
{
public function getName() { return 'OperatorsExtension'; }
public function getOperators()
{
return array(
array(),
array('>>' => array(
'precedence' => 20,
'class' => 'DesymfonyOperatorsMaxOperator',
'associativity' => Twig_ExpressionParser::OPERATOR_LEFT
)
));
}
}
107. 2 +3**2 / 4 .. 4-5//2
Operador Precedence
!= 20
+ 30
** 200
108. {{ a >> b }} max($a, $b);
TWIG PHP
109. class MaxOperator extends Twig_Node_Expression_Binary
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('max(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
110. class MaxOperator extends Twig_Node_Expression_Binary
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('max(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
max($a, $b);
111. // line 23
echo twig_escape_filter($this->env,
max(5, 2), "html", null, true
);
112. $compiler
->raw() (literal)
->write() (indentado)
->string() (entrecomillado)
->indent() (indentar)
->outdent() (desindentar)
;
113. Operador "cambia claves por valores"
PHP array_flip(range('a', 'z'))
TWIG {% for i in <->('a'..'z') %}
{{ i }},
{% endfor %}
114. Operador "cambia claves por valores"
PHP array_flip(range('a', 'z'))
TWIG {% for i in <->('a'..'z') %}
{{ i }},
{% endfor %}
115. class OperatorsExtension extends Twig_Extension
{
public function getName() { return 'OperatorsExtension'; }
public function getOperators()
{
return array(
array('<->' => array(
'precedence' => 50,
'class' => 'DesymfonyOperatorsFlipOperator',
),
array()
));
}
}
116. class OperatorsExtension extends Twig_Extension
{
public function getName() { return 'OperatorsExtension'; }
public function getOperators()
{
return array(
array('<->' => array(
'precedence' => 50,
'class' => 'DesymfonyOperatorsFlipOperator',
),
array()
));
}
}
117. class OperatorsExtension extends Twig_Extension
{
public function getName() { return 'OperatorsExtension'; }
public function getOperators()
{
return array(
array('<->' => array(
'precedence' => 50,
'class' => 'DesymfonyOperatorsFlipOperator',
),
array()
));
}
}
119. namespace DesymfonyOperators;
class FlipOperator extends Twig_Node_Expression_Unary
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw("array_flip(")
->subcompile($this->getNode('node'))
->raw(")")
;
}
array_flip($coleccion);
120. // line 17
$context['_parent'] = (array) $context;
$context['_seq'] = twig_ensure_traversable(array_flip(range("a", "z")));
foreach ($context['_seq'] as $context["_key"] => $context["i"]) {
// line 18
echo " ";
if (isset($context["i"])) { $_i_ = $context["i"]; } else { $_i_ = null; }
echo twig_escape_filter($this->env, $_i_, "html", null, true);
echo ",";
}
121. // line 17
$context['_parent'] = (array) $context;
$context['_seq'] = twig_ensure_traversable(array_flip(range("a", "z")));
foreach ($context['_seq'] as $context["_key"] => $context["i"]) {
// line 18
echo " ";
if (isset($context["i"])) { $_i_ = $context["i"]; } else { $_i_ = null; }
echo twig_escape_filter($this->env, $_i_, "html", null, true);
echo ",";
}
123. # mkfs -q /dev/ram1 65536
# mkdir -p /twigcache
# mount /dev/ram1 /twigcache
Inspirado por: http://www.cyberciti.biz/faq/howto!create!linux!ram!disk!filesystem/
124. $twig = new Twig_Environment(
$loader,
array('cache' => '/twigcache')
);
# app/config/config.yml
twig:
cache: '/twigcache'
125. ETIQUETAS
PROPIAS
1.* BÁSICO INTERMEDIO AVANZADO
129. $loader = new Twig_Loader_Filesystem(...);
$twig = new Twig_Environment($loader, array(...));
$twig->addTokenParser(new SourceTokenParser());
130. {% source '...' %}
class SourceTokenParser extends Twig_TokenParser
{
public function getTag()
{
return 'source';
}
}
131. namespace DesymfonyTags;
use DesymfonyTagsSourceNode;
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());
}
}
132. 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(');');
;
}
}
133. // line 3
echo file_get_contents("simple.twig");
// line 4
echo "n";
// line 5
echo file_get_contents("../../../composer.json");
// line 6
echo "n";
135. La oferta cuesta 25.78 euros
(30.42 con IVA) y es válida
hasta el 10/06/2012
136. La oferta cuesta 25.78 euros
(30.42 con IVA) y es válida
hasta el 10/06/2012
137. La oferta cuesta 25.78 euros (30.42 con IVA) y es
válida hasta el 10/06/2012
~
{{ 'La oferta cuesta ' ~ oferta.precio ~ ' euros (' ~
oferta.precio*1.18 ~ ' con IVA) y es válida hasta el ' ~
oferta.fechaExpiracion|date() }}
138. La oferta cuesta 25.78 euros (30.42 con IVA) y es
válida hasta el 10/06/2012
format()
{{ 'La oferta cuesta %.2f euros (%.2f con IVA) y es
válida hasta el %s'|format(oferta.precio,
oferta.precio*1.18, oferta.fechaExpiracion|date()) }}
139. La oferta cuesta 25.78 euros (30.42 con IVA) y es
válida hasta el 10/06/2012
replace()
{{ 'La oferta cuesta :precio euros (:total con IVA) y es
válida hasta el :fecha'|replace({ ':precio':
oferta.precio, ':total': oferta.precio*1.18, ':fecha':
oferta.fechaExpiracion|date() }) }}
140. La oferta cuesta 25.78 euros (30.42 con IVA) y es
válida hasta el 10/06/2012
{{ "La oferta cuesta #{oferta.precio}
euros (#{oferta.precio*1.18} con IVA)
y es válida hasta el
#{oferta.fechaExpiracion|date()}" }}
146. {{ 1 + 1 }} 2
{% do 1 + 1 %} (nada)
147. {% set lista = ['a', 'b', 'c', 'd'] %}
{{ lista|shift }}
{% do lista|shift %}
Fuente: https://github.com/fabpot/Twig/issues/446
150. {% set lista = ['a', 'b', 'c', 'd'] %}
{{ lista[-1:] }}
151. {% set lista = ['a', 'b', 'c', 'd'] %}
{{ lista[2:2] }}
155. <html>
<head> ... </head>
<body>
...
<span data-host="Darwin mbp.local 10.8.0 Darwin Kernel Version
10.8.0: Tue Jun 7 16:33:36 PDT 2011; root:xnu-1504.15.3~1/
RELEASE_I386 i386" data-elapsed="0.97804594039917 sec."
data-timestamp="1339609672.9781"></span>
</body>
</html>
158. app/cache/dev/twig/09/fc/2d8a188dda8245d295e6324582f2.php
<?php
/* ::frontend.html.twig */
class __TwigTemplate_09fc2d8a188dda8245d295e6324582f2
extends Twig_Template
{
public function __construct(Twig_Environment $env)
{
parent::__construct($env);
$this->parent = $this->env->loadTemplate("::base.html.twig");
$this->blocks = array(
'stylesheets' => array($this, 'block_stylesheets'),
'javascripts' => array($this, 'block_javascripts'),
'body' => array($this, 'block_body'),
'article' => array($this, 'block_article'),
165. <html>
<head> ... </head>
<body>
...
<span data-host="Darwin mbp.local 10.8.0 Darwin Kernel Version
10.8.0: Tue Jun 7 16:33:36 PDT 2011; root:xnu-1504.15.3~1/
RELEASE_I386 i386" data-elapsed="0.97804594039917 sec."
data-timestamp="1339609672.9781"></span>
</body>
</html>
166. base_template_class
$loader = new Twig_Loader_Filesystem(__DIR__.'/../Resources/views');
$twig = new Twig_Environment($loader, array(
'base_template_class' => 'DesymfonyTemplateMiTwigTemplate',
));
# app/config/config.yml
twig:
base_template_class: "DesymfonyTemplateMiTwigTemplate"
167. namespace DesymfonyTemplate;
abstract class MiTwigTemplate extends Twig_Template
{
public function render(array $context)
{
$traza = 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>', $traza."n</body>",
parent::render($context)
);
}
}
169. $twig = new Twig_Environment($loader, array(..));
try {
$twig->parse($twig->tokenize($plantilla));
echo "[OK] La sintaxis de la plantilla es correcta";
} catch (Twig_Error_Syntax $e) {
echo "[ERROR] La plantilla tiene errores de sintaxis";
}
170. $ php app/console twig:lint @MyBundle
$ php app/console twig:lint src/Cupon/
OfertaBundle/Resources/views/
index.html.twig