mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-19 05:46:41 +00:00
added macro support
git-svn-id: http://svn.twig-project.org/trunk@65 93ef8e89-cb99-4229-a87c-7fa0fa45744b
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
* 0.9.2-DEV
|
||||
|
||||
* added macro support
|
||||
* changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance
|
||||
* made Twig_Autoloader::autoload() a static method
|
||||
* avoid writing template file if an error occurs
|
||||
* added $ escaping when outputting raw strings
|
||||
|
||||
@@ -345,7 +345,7 @@ When automatic escaping is enabled everything is escaped by default except for
|
||||
values explicitly marked as safe. Those can be marked in the template by using
|
||||
the `|safe` filter.
|
||||
|
||||
Functions returning template data (like `parent`) return safe markup always.
|
||||
Functions returning template data (like macros and `parent`) return safe markup always.
|
||||
|
||||
>**NOTE**
|
||||
>Twig is smart enough to not escape an already escaped value by the `escape`
|
||||
@@ -423,6 +423,45 @@ more complex `expressions` there too:
|
||||
Kenny looks okay --- so far
|
||||
{% endif %}
|
||||
|
||||
### Macros
|
||||
|
||||
Macros are comparable with functions in regular programming languages. They
|
||||
are useful to put often used HTML idioms into reusable functions to not repeat
|
||||
yourself.
|
||||
|
||||
Here a small example of a macro that renders a form element:
|
||||
|
||||
[twig]
|
||||
{% macro input(name, value, type, size) %}
|
||||
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
||||
{% endmacro %}
|
||||
|
||||
Macros differs from native PHP functions in a few ways:
|
||||
|
||||
* Default argument values are defined by using the `default` filter in the
|
||||
macro body;
|
||||
|
||||
* Arguments of a macro are always optional.
|
||||
|
||||
But as PHP functions, macros don't have access to the current template
|
||||
variables.
|
||||
|
||||
Macros can be defined in any template, and always need to be "imported" before
|
||||
being used (see the Import section for more information):
|
||||
|
||||
[twig]
|
||||
{% import "forms.html" as forms %}
|
||||
|
||||
The above `import` call imports the "forms.html" file (which can contain only
|
||||
macros, or a template and some macros), and import the functions as items of
|
||||
the `forms` variable.
|
||||
|
||||
The macro can then be called at will:
|
||||
|
||||
[twig]
|
||||
<p>{{ forms.input('username') }}</p>
|
||||
<p>{{ forms.input('password', null, 'password') }}</p>
|
||||
|
||||
### Filters
|
||||
|
||||
Filter sections allow you to apply regular Twig filters on a block of template
|
||||
@@ -464,6 +503,48 @@ An included file can be evaluated in the sandbox environment by appending
|
||||
[twig]
|
||||
{% include 'user.html' sandboxed %}
|
||||
|
||||
### Import
|
||||
|
||||
Twig supports putting often used code into macros. These macros can go into
|
||||
different templates and get imported from there.
|
||||
|
||||
Imagine we have a helper module that renders forms (called `forms.html`):
|
||||
|
||||
[twig]
|
||||
{% macro input(name, value, type, size) %}
|
||||
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
|
||||
{% endmacro %}
|
||||
|
||||
{% macro textarea(name, value, rows) %}
|
||||
<textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
|
||||
{% endmacro %}
|
||||
|
||||
Importing these macros in a template is as easy as using the `import` tag:
|
||||
|
||||
[twig]
|
||||
{% import 'forms.html' as forms %}
|
||||
<dl>
|
||||
<dt>Username</dt>
|
||||
<dd>{{ forms.input('username') }}</dd>
|
||||
<dt>Password</dt>
|
||||
<dd>{{ forms.input('password', null, 'password') }}</dd>
|
||||
</dl>
|
||||
<p>{{ forms.textarea('comment') }}</p>
|
||||
|
||||
Even if the macros are defined in the same template as the one where you want
|
||||
to use them, they still need to be imported:
|
||||
|
||||
[twig]
|
||||
{# index.html template #}
|
||||
|
||||
{% macro textarea(name, value, rows) %}
|
||||
<textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
|
||||
{% endmacro %}
|
||||
|
||||
{% import "index.html" as forms %}
|
||||
|
||||
<p>{{ forms.textarea('comment') }}</p>
|
||||
|
||||
Expressions
|
||||
-----------
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ class Twig_Extension_Core extends Twig_Extension
|
||||
new Twig_TokenParser_Parent(),
|
||||
new Twig_TokenParser_Display(),
|
||||
new Twig_TokenParser_Filter(),
|
||||
new Twig_TokenParser_Macro(),
|
||||
new Twig_TokenParser_Import(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) 2009 Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
abstract class Twig_Macro implements Twig_MacroInterface
|
||||
{
|
||||
protected $env;
|
||||
|
||||
public function __construct(Twig_Environment $env)
|
||||
{
|
||||
$this->env = $env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bound environment for this template.
|
||||
*
|
||||
* @return Twig_Environment The current environment
|
||||
*/
|
||||
public function getEnvironment()
|
||||
{
|
||||
return $this->env;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) 2009 Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
interface Twig_MacroInterface
|
||||
{
|
||||
/**
|
||||
* Returns the bound environment for this template.
|
||||
*
|
||||
* @return Twig_Environment The current environment
|
||||
*/
|
||||
public function getEnvironment();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) 2009 Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an import node.
|
||||
*
|
||||
* @package twig
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
* @version SVN: $Id$
|
||||
*/
|
||||
class Twig_Node_Import extends Twig_Node
|
||||
{
|
||||
protected $macro;
|
||||
protected $var;
|
||||
|
||||
public function __construct($macro, $var, $lineno, $tag = null)
|
||||
{
|
||||
parent::__construct($lineno, $tag);
|
||||
$this->macro = $macro;
|
||||
$this->var = $var;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return get_class($this).'('.$this->macro.', '.$this->var.')';
|
||||
}
|
||||
|
||||
public function compile($compiler)
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('$this->env->getLoader()->load(')
|
||||
->string($this->macro)
|
||||
->raw(");\n\n")
|
||||
->write("if (!class_exists(")
|
||||
->string('__TwigMacro_'.md5($this->macro))
|
||||
->raw("))\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->write(sprintf("throw new InvalidArgumentException('There is no defined macros in template \"%s\".');\n", $this->macro))
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
->write(sprintf("\$context["))
|
||||
->string($this->var)
|
||||
->raw(sprintf("] = new __TwigMacro_%s(\$this->env);\n", md5($this->macro)))
|
||||
;
|
||||
}
|
||||
|
||||
public function getMacro()
|
||||
{
|
||||
return $this->macro;
|
||||
}
|
||||
|
||||
public function getVar()
|
||||
{
|
||||
return $this->var;
|
||||
}
|
||||
}
|
||||
+49
-7
@@ -20,17 +20,29 @@ class Twig_Node_Macro extends Twig_Node implements Twig_NodeListInterface
|
||||
{
|
||||
protected $name;
|
||||
protected $body;
|
||||
protected $arguments;
|
||||
|
||||
public function __construct($name, Twig_NodeList $body, $lineno, $tag = null)
|
||||
public function __construct($name, Twig_NodeList $body, $arguments, $lineno, $parent = null, $tag = null)
|
||||
{
|
||||
parent::__construct($lineno, $tag);
|
||||
$this->name = $name;
|
||||
$this->body = $body;
|
||||
$this->body = $body;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return get_class($this).'('.$this->name.')';
|
||||
$repr = array(get_class($this).' '.$this->name.'(');
|
||||
foreach ($this->body->getNodes() as $node)
|
||||
{
|
||||
foreach (explode("\n", $node->__toString()) as $line)
|
||||
{
|
||||
$repr[] = ' '.$line;
|
||||
}
|
||||
}
|
||||
$repr[] = ')';
|
||||
|
||||
return implode("\n", $repr);
|
||||
}
|
||||
|
||||
public function getNodes()
|
||||
@@ -43,13 +55,43 @@ class Twig_Node_Macro extends Twig_Node implements Twig_NodeListInterface
|
||||
$this->body = new Twig_NodeList($nodes, $this->lineno);
|
||||
}
|
||||
|
||||
public function compile($compiler)
|
||||
public function replace($other)
|
||||
{
|
||||
$compiler->subcompile($this->body);
|
||||
$this->body = $other->body;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
public function compile($compiler)
|
||||
{
|
||||
return $this->name;
|
||||
$arguments = array();
|
||||
foreach ($this->arguments as $argument)
|
||||
{
|
||||
$arguments[] = '$'.$argument->getName().' = null';
|
||||
}
|
||||
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write(sprintf("public function get%s(%s)\n", $this->name, implode(', ', $arguments)), "{\n")
|
||||
->indent()
|
||||
->write("\$context = array(\n")
|
||||
->indent()
|
||||
;
|
||||
|
||||
foreach ($this->arguments as $argument)
|
||||
{
|
||||
$compiler
|
||||
->write('')
|
||||
->string($argument->getName())
|
||||
->raw(' => $'.$argument->getName())
|
||||
->raw(",\n")
|
||||
;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->outdent()
|
||||
->write(");\n\n")
|
||||
->subcompile($this->body)
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
+61
-13
@@ -22,17 +22,19 @@ class Twig_Node_Module extends Twig_Node implements Twig_NodeListInterface
|
||||
protected $body;
|
||||
protected $extends;
|
||||
protected $blocks;
|
||||
protected $macros;
|
||||
protected $filename;
|
||||
protected $usedFilters;
|
||||
protected $usedTags;
|
||||
|
||||
public function __construct(Twig_NodeList $body, $extends, $blocks, $filename)
|
||||
public function __construct(Twig_NodeList $body, $extends, array $blocks, array $macros, $filename)
|
||||
{
|
||||
parent::__construct(1);
|
||||
|
||||
$this->body = $body;
|
||||
$this->extends = $extends;
|
||||
$this->blocks = array_values($blocks);
|
||||
$this->macros = $macros;
|
||||
$this->filename = $filename;
|
||||
$this->usedFilters = array();
|
||||
$this->usedTags = array();
|
||||
@@ -83,6 +85,12 @@ class Twig_Node_Module extends Twig_Node implements Twig_NodeListInterface
|
||||
}
|
||||
|
||||
public function compile($compiler)
|
||||
{
|
||||
$this->compileTemplate($compiler);
|
||||
$this->compileMacros($compiler);
|
||||
}
|
||||
|
||||
protected function compileTemplate($compiler)
|
||||
{
|
||||
$sandboxed = $compiler->getEnvironment()->hasExtension('sandbox');
|
||||
|
||||
@@ -102,24 +110,38 @@ class Twig_Node_Module extends Twig_Node implements Twig_NodeListInterface
|
||||
->write('class __TwigTemplate_'.md5($this->filename))
|
||||
;
|
||||
|
||||
if (!is_null($this->extends))
|
||||
$parent = null === $this->extends ? $compiler->getEnvironment()->getBaseTemplateClass() : '__TwigTemplate_'.md5($this->extends);
|
||||
|
||||
$compiler
|
||||
->raw(" extends $parent\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->write("public function display(array \$context)\n", "{\n")
|
||||
->indent()
|
||||
;
|
||||
|
||||
if (null !== $this->extends)
|
||||
{
|
||||
$parent = md5($this->extends);
|
||||
// remove all but import nodes
|
||||
$nodes = array();
|
||||
foreach ($this->body->getNodes() as $node)
|
||||
{
|
||||
if ($node instanceof Twig_Node_Import)
|
||||
{
|
||||
$nodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw(" extends __TwigTemplate_$parent\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->subcompile(new Twig_NodeList($nodes))
|
||||
->write("\n")
|
||||
->write("parent::display(\$context);\n")
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
;
|
||||
}
|
||||
else
|
||||
{
|
||||
$compiler
|
||||
->write(" extends ".$compiler->getEnvironment()->getBaseTemplateClass()."\n", "{\n")
|
||||
->indent()
|
||||
->write("public function display(array \$context)\n", "{\n")
|
||||
->indent()
|
||||
;
|
||||
|
||||
if ($sandboxed)
|
||||
{
|
||||
$compiler->write("\$this->checkSecurity();\n");
|
||||
@@ -175,4 +197,30 @@ class Twig_Node_Module extends Twig_Node implements Twig_NodeListInterface
|
||||
->write("}\n")
|
||||
;
|
||||
}
|
||||
|
||||
protected function compileMacros($compiler)
|
||||
{
|
||||
if (!$this->macros)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write("\n")
|
||||
->write('class __TwigMacro_'.md5($this->filename).' extends Twig_Macro'."\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
;
|
||||
|
||||
// macros
|
||||
foreach ($this->macros as $node)
|
||||
{
|
||||
$compiler->subcompile($node);
|
||||
}
|
||||
|
||||
$compiler
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-1
@@ -18,6 +18,7 @@ class Twig_Parser
|
||||
protected $expressionParser;
|
||||
protected $blocks;
|
||||
protected $currentBlock;
|
||||
protected $macros;
|
||||
protected $env;
|
||||
|
||||
public function __construct(Twig_Environment $env = null)
|
||||
@@ -61,6 +62,7 @@ class Twig_Parser
|
||||
$this->stream = $stream;
|
||||
$this->extends = null;
|
||||
$this->blocks = array();
|
||||
$this->macros = array();
|
||||
$this->currentBlock = null;
|
||||
|
||||
try
|
||||
@@ -85,7 +87,7 @@ class Twig_Parser
|
||||
}
|
||||
}
|
||||
|
||||
$node = new Twig_Node_Module($body, $this->extends, $this->blocks, $this->stream->getFilename());
|
||||
$node = new Twig_Node_Module($body, $this->extends, $this->blocks, $this->macros, $this->stream->getFilename());
|
||||
|
||||
$transformer = new Twig_NodeTransformer_Chain($this->transformers);
|
||||
$transformer->setEnvironment($this->env);
|
||||
@@ -186,6 +188,16 @@ class Twig_Parser
|
||||
$this->blocks[$name] = $value;
|
||||
}
|
||||
|
||||
public function hasMacro($name)
|
||||
{
|
||||
return isset($this->macros[$name]);
|
||||
}
|
||||
|
||||
public function setMacro($name, $value)
|
||||
{
|
||||
$this->macros[$name] = $value;
|
||||
}
|
||||
|
||||
public function getExpressionParser()
|
||||
{
|
||||
return $this->expressionParser;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) 2009 Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
class Twig_TokenParser_Import extends Twig_TokenParser
|
||||
{
|
||||
public function parse(Twig_Token $token)
|
||||
{
|
||||
$macro = $this->parser->getStream()->expect(Twig_Token::STRING_TYPE)->getValue();
|
||||
$this->parser->getStream()->expect('as');
|
||||
$var = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
|
||||
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
return new Twig_Node_Import($macro, $var, $token->getLine(), $this->getTag());
|
||||
}
|
||||
|
||||
public function getTag()
|
||||
{
|
||||
return 'import';
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,15 @@ class Twig_TokenParser_Macro extends Twig_TokenParser
|
||||
$lineno = $token->getLine();
|
||||
$name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
|
||||
|
||||
// arguments
|
||||
|
||||
$arguments = $this->parser->getExpressionParser()->parseArguments();
|
||||
|
||||
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
|
||||
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
return new Twig_Node_Macro($name, $body, $lineno, $this->getTag());
|
||||
$this->parser->setMacro($name, new Twig_Node_Macro($name, $body, $arguments, $lineno, $this->getTag()));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function decideBlockEnd($token)
|
||||
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
"macro" tag
|
||||
--TEMPLATE--
|
||||
{% import '%prefix%index.twig' as forms %}
|
||||
|
||||
{{ forms.input('username') }}
|
||||
{{ forms.input('password', null, 'password', 1) }}
|
||||
|
||||
{% macro input(name, value, type, size) %}
|
||||
<input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
|
||||
{% endmacro %}
|
||||
--DATA--
|
||||
return array()
|
||||
--EXPECT--
|
||||
<input type="text" name="username" value="" size="20">
|
||||
|
||||
<input type="password" name="password" value="" size="1">
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
"macro" tag
|
||||
--TEMPLATE--
|
||||
{% import '%prefix%forms.twig' as forms %}
|
||||
|
||||
{{ forms.input('username') }}
|
||||
{{ forms.input('password', null, 'password', 1) }}
|
||||
--TEMPLATE(forms.twig)--
|
||||
{% macro input(name, value, type, size) %}
|
||||
<input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
|
||||
{% endmacro %}
|
||||
--DATA--
|
||||
return array()
|
||||
--EXPECT--
|
||||
<input type="text" name="username" value="" size="20">
|
||||
|
||||
<input type="password" name="password" value="" size="1">
|
||||
@@ -35,7 +35,7 @@ class Foo
|
||||
}
|
||||
}
|
||||
|
||||
$t = new LimeTest(44);
|
||||
$t = new LimeTest(46);
|
||||
$fixturesDir = realpath(dirname(__FILE__).'/../fixtures/');
|
||||
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
|
||||
|
||||
Reference in New Issue
Block a user