added the inline tag

This commit is contained in:
Fabien Potencier
2012-04-23 13:03:38 +02:00
parent f79a56b431
commit 2fa8224be4
16 changed files with 404 additions and 45 deletions
+4
View File
@@ -1,3 +1,7 @@
* 1.8.0 (2012-XX-XX)
* added an inline tag
* 1.7.0 (2012-XX-XX)
* fixed template line number in some exceptions
+1
View File
@@ -21,3 +21,4 @@ Tags
flush
do
sandbox
inline
+127
View File
@@ -0,0 +1,127 @@
``inline``
==========
.. versionadded:: 1.8
The ``inline`` tag was added in Twig 1.8.
The ``inline`` statement allows you to inline a template instead of including
it from an external file (like with the ``include`` statement):
.. code-block:: jinja
{% inline %}
{% extends "sidebar.twig" %}
{% block content %}
Some content for the sidebar
{% endblock %}
{% endinline %}
As it's not easy to understand in which circumstances it might come in handy,
let's take an example; imagine a base template shared by many pages with a
single block::
┌─── Page n ──────────────────────────┐
│ │
│ ┌─────────────────────┐ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────┘
Even pages (page 2, 4, ...) share the same structure for the block::
┌─── Page 2 & 4 ──────────────────────┐
│ │
│ ┌── Base even ────────┐ │
│ │ ┌─────────────────┐ │ │
│ │ │ Content 2, ... │ │ │
│ │ └─────────────────┘ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Content 2, ... │ │ │
│ │ └─────────────────┘ │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────┘
And odd pages (page 1, 3, ...) share a different structure for the block::
┌─── Page 1 & 3 ──────────────────────┐
│ │
│ ┌── Base odd ─────────┐ │
│ │ ┌───────┐ ┌───────┐ │ │
│ │ │ │ │ │ │ │
│ │ │content│ │content│ │ │
│ │ │3, ... │ │3, ... │ │ │
│ │ │ │ │ │ │ │
│ │ └───────┘ └───────┘ │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────┘
Without the ``inline`` tag, you have two ways to design your templates:
* Create two base templates (one for even blocks and another one for odd
blocks) to factor out the common template code, then one template for each
page that inherits from one of the base template;
* Inline the each custom page content directly into each page without any use
of external templates (you need to repeat the common code for all
templates).
These two solutions do not scale well because they each have a major drawback:
* The first solution makes you create many external files (that you won't
re-use anywhere else) and so it fails to keep your templates readable (many
code and content are out of context);
* The second solution makes you duplicate some common code from one template
to another (so it fails to obey the "Don't repeat yourself" principle).
In such a situation, the ``inline`` tag fixes all these issues. The common
code can be factored out in base templates (as in solution 1), and the custom
content is kept in each page (as in solution 2):
.. code-block:: jinja
{# template for an even template: page 2 #}
{% extends page %}
{% block base %}
{% inline %}
{% extends "even.twig" %}
{% block content1 %}
Content 1 for page 2
{% endblock %}
{% block content2 %}
Content 2 for page 2
{% endblock %}
{% endinline %}
{% endblock %}
The ``inline`` tag can be customized with the same options (``with``,
``only``, ``ignore missing``) as the ``include`` tag:
.. code-block:: jinja
{% inline with {'foo': 'bar'} %}
...
{% endinline %}
{% inline with {'foo': 'bar'} only %}
...
{% endinline %}
{% inline ignore missing %}
...
{% endinline %}
.. seealso:: :doc:`include<../tags/include>`
+8 -6
View File
@@ -251,13 +251,14 @@ class Twig_Environment
/**
* Gets the template class associated with the given string.
*
* @param string $name The name for which to calculate the template class name
* @param string $name The name for which to calculate the template class name
* @param integer $indice The indice for inline templates (null for main templates)
*
* @return string The template class name
*/
public function getTemplateClass($name)
public function getTemplateClass($name, $indice = null)
{
return $this->templateClassPrefix.md5($this->loader->getCacheKey($name));
return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)).(null === $indice ? '' : '_'.$indice);
}
/**
@@ -297,13 +298,14 @@ class Twig_Environment
/**
* Loads a template by name.
*
* @param string $name The template name
* @param string $name The template name
* @param integer $indice The indice for inline templates (null for main templates)
*
* @return Twig_TemplateInterface A template instance representing the given template name
*/
public function loadTemplate($name)
public function loadTemplate($name, $indice = null)
{
$cls = $this->getTemplateClass($name);
$cls = $this->getTemplateClass($name, $indice);
if (isset($this->loadedTemplates[$cls])) {
return $this->loadedTemplates[$cls];
+1
View File
@@ -109,6 +109,7 @@ class Twig_Extension_Core extends Twig_Extension
new Twig_TokenParser_Spaceless(),
new Twig_TokenParser_Flush(),
new Twig_TokenParser_Do(),
new Twig_TokenParser_Inline(),
);
}
+27 -15
View File
@@ -39,21 +39,46 @@ class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface
;
}
$this->addGetTemplate($compiler);
$compiler->raw('->display(');
$this->addTemplateArguments($compiler);
$compiler->raw(");\n");
if ($this->getAttribute('ignore_missing')) {
$compiler
->outdent()
->write("} catch (Twig_Error_Loader \$e) {\n")
->indent()
->write("// ignore missing template\n")
->outdent()
->write("}\n\n")
;
}
}
protected function addGetTemplate(Twig_Compiler $compiler)
{
if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) {
$compiler
->write("\$this->env->loadTemplate(")
->subcompile($this->getNode('expr'))
->raw(")->display(")
->raw(")")
;
} else {
$compiler
->write("\$template = \$this->env->resolveTemplate(")
->subcompile($this->getNode('expr'))
->raw(");\n")
->write('$template->display(')
->write('$template')
;
}
}
protected function addTemplateArguments(Twig_Compiler $compiler)
{
if (false === $this->getAttribute('only')) {
if (null === $this->getNode('variables')) {
$compiler->raw('$context');
@@ -71,18 +96,5 @@ class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface
$compiler->subcompile($this->getNode('variables'));
}
}
$compiler->raw(");\n");
if ($this->getAttribute('ignore_missing')) {
$compiler
->outdent()
->write("} catch (Twig_Error_Loader \$e) {\n")
->indent()
->write("// ignore missing template\n")
->outdent()
->write("}\n\n")
;
}
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Represents an include node for an inlined template.
*
* @package twig
* @author Fabien Potencier <fabien@symfony.com>
*/
class Twig_Node_Inline extends Twig_Node_Include
{
// we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)
public function __construct($filename, $inline, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
{
parent::__construct(new Twig_Node_Expression_Constant('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
$this->setAttribute('filename', $filename);
$this->setAttribute('inline', $inline);
}
protected function addGetTemplate(Twig_Compiler $compiler)
{
$compiler
->write("\$this->env->loadTemplate(")
->string($this->getAttribute('filename'))
->raw(', ')
->string($this->getAttribute('inline'))
->raw(")")
;
}
}
+17 -4
View File
@@ -18,9 +18,14 @@
*/
class Twig_Node_Module extends Twig_Node
{
public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $filename)
public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, Twig_NodeInterface $inlinedTemplates, $filename)
{
parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename), 1);
parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits, 'inlined_templates' => $inlinedTemplates), array('filename' => $filename, 'inline' => null), 1);
}
public function setInline($indice)
{
$this->setAttribute('inline', $indice);
}
/**
@@ -31,10 +36,18 @@ class Twig_Node_Module extends Twig_Node
public function compile(Twig_Compiler $compiler)
{
$this->compileTemplate($compiler);
foreach ($this->getNode('inlined_templates') as $template) {
$compiler->subcompile($template);
}
}
protected function compileTemplate(Twig_Compiler $compiler)
{
if (!$this->getAttribute('inline')) {
$compiler->write('<?php');
}
$this->compileClassHeader($compiler);
if (count($this->getNode('blocks')) || count($this->getNode('traits')) || null === $this->getNode('parent') || $this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
@@ -108,10 +121,10 @@ class Twig_Node_Module extends Twig_Node
protected function compileClassHeader(Twig_Compiler $compiler)
{
$compiler
->write("<?php\n\n")
->write("\n\n")
// if the filename contains */, add a blank to avoid a PHP parse error
->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n")
->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename')))
->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('inline')))
->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
->write("{\n")
->indent()
+3 -1
View File
@@ -24,7 +24,9 @@ class Twig_Node_SandboxedModule extends Twig_Node_Module
public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions)
{
parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getNode('traits'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag());
parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getNode('traits'), $node->getNode('inlined_templates'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag());
$this->setAttribute('inline', $node->getAttribute('inline'));
$this->usedFilters = $usedFilters;
$this->usedTags = $usedTags;
+11 -3
View File
@@ -32,6 +32,7 @@ class Twig_Parser implements Twig_ParserInterface
protected $importedFunctions;
protected $tmpVarCount;
protected $traits;
protected $inlinedTemplates = array();
/**
* Constructor.
@@ -60,7 +61,7 @@ class Twig_Parser implements Twig_ParserInterface
*
* @return Twig_Node_Module A node tree
*/
public function parse(Twig_TokenStream $stream)
public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false)
{
// push all variables into the stack to keep the current state of the parser
$vars = get_object_vars($this);
@@ -93,7 +94,7 @@ class Twig_Parser implements Twig_ParserInterface
$this->importedFunctions = array(array());
try {
$body = $this->subparse(null);
$body = $this->subparse($test, $dropNeedle);
if (null !== $this->parent) {
if (null === $body = $this->filterBodyNodes($body)) {
@@ -108,7 +109,7 @@ class Twig_Parser implements Twig_ParserInterface
throw $e;
}
$node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->stream->getFilename());
$node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), new Twig_Node($this->inlinedTemplates), $this->stream->getFilename());
$traverser = new Twig_NodeTraverser($this->env, $this->visitors);
@@ -271,6 +272,13 @@ class Twig_Parser implements Twig_ParserInterface
return count($this->traits) > 0;
}
public function addInlinedTemplate(Twig_Node_Module $template)
{
$template->setInline(count($this->inlinedTemplates) + 1);
$this->inlinedTemplates[] = $template;
}
public function addImportedFunction($alias, $name, Twig_Node_Expression $node)
{
$this->importedFunctions[0][$alias] = array('name' => $name, 'node' => $node);
+18 -9
View File
@@ -32,31 +32,40 @@ class Twig_TokenParser_Include extends Twig_TokenParser
{
$expr = $this->parser->getExpressionParser()->parseExpression();
list($variables, $only, $ignoreMissing) = $this->parseArguments();
return new Twig_Node_Include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
}
protected function parseArguments()
{
$stream = $this->parser->getStream();
$ignoreMissing = false;
if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'ignore')) {
$this->parser->getStream()->next();
$this->parser->getStream()->expect(Twig_Token::NAME_TYPE, 'missing');
if ($stream->test(Twig_Token::NAME_TYPE, 'ignore')) {
$stream->next();
$stream->expect(Twig_Token::NAME_TYPE, 'missing');
$ignoreMissing = true;
}
$variables = null;
if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'with')) {
$this->parser->getStream()->next();
if ($stream->test(Twig_Token::NAME_TYPE, 'with')) {
$stream->next();
$variables = $this->parser->getExpressionParser()->parseExpression();
}
$only = false;
if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'only')) {
$this->parser->getStream()->next();
if ($stream->test(Twig_Token::NAME_TYPE, 'only')) {
$stream->next();
$only = true;
}
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return new Twig_Node_Include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
return array($variables, $only, $ignoreMissing);
}
/**
+50
View File
@@ -0,0 +1,50 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2012 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Includes an inline template.
*/
class Twig_TokenParser_Inline extends Twig_TokenParser_Include
{
/**
* Parses a token and returns a node.
*
* @param Twig_Token $token A Twig_Token instance
*
* @return Twig_NodeInterface A Twig_NodeInterface instance
*/
public function parse(Twig_Token $token)
{
list($variables, $only, $ignoreMissing) = $this->parseArguments();
$module = $this->parser->parse($this->parser->getStream(), array($this, 'decideBlockEnd'), true);
$this->parser->addInlinedTemplate($module);
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
return new Twig_Node_Inline($module->getAttribute('filename'), $module->getAttribute('inline'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
}
public function decideBlockEnd(Twig_Token $token)
{
return $token->test('endinline');
}
/**
* Gets the tag name associated with this token parser.
*
* @return string The tag name
*/
public function getTag()
{
return 'inline';
}
}
@@ -0,0 +1,37 @@
--TEST--
"inline" tag
--TEMPLATE--
FOO
{% inline %}
{% extends "foo.twig" %}
{% block c1 %}
{{ parent() }}
block1extended
{% endblock %}
{% endinline %}
BAR
--TEMPLATE(foo.twig)--
A
{% block c1 %}
block1
{% endblock %}
B
{% block c2 %}
block2
{% endblock %}
C
--DATA--
return array()
--EXPECT--
FOO
A
block1
block1extended
B
block2
C
BAR
@@ -0,0 +1,54 @@
--TEST--
"inline" tag
--TEMPLATE--
{% extends "base.twig" %}
{% block c1 %}
{{ parent() }}
blockc1baseextended
{% endblock %}
{% block c2 %}
{% inline %}
{% extends "foo.twig" %}
{% block c1 %}
{{ parent() }}
block1extended
{% endblock %}
{% endinline %}
{% endblock %}
--TEMPLATE(base.twig)--
A
{% block c1 %}
blockc1base
{% endblock %}
{% block c2 %}
block2base
{% endblock %}
B
--TEMPLATE(foo.twig)--
A
{% block c1 %}
block1
{% endblock %}
B
{% block c2 %}
block2
{% endblock %}
C
--DATA--
return array()
--EXPECT--
A
blockc1base
blockc1baseextended
A
block1
block1extended
B
block2
CB
+4 -4
View File
@@ -24,7 +24,7 @@ class Twig_Tests_Node_ModuleTest extends Twig_Tests_Node_TestCase
$macros = new Twig_Node();
$traits = new Twig_Node();
$filename = 'foo.twig';
$node = new Twig_Node_Module($body, $parent, $blocks, $macros, $traits, $filename);
$node = new Twig_Node_Module($body, $parent, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
$this->assertEquals($body, $node->getNode('body'));
$this->assertEquals($blocks, $node->getNode('blocks'));
@@ -62,7 +62,7 @@ class Twig_Tests_Node_ModuleTest extends Twig_Tests_Node_TestCase
$traits = new Twig_Node();
$filename = 'foo.twig';
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, $filename);
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
$tests[] = array($node, <<<EOF
<?php
@@ -102,7 +102,7 @@ EOF
$body = new Twig_Node(array($import));
$extends = new Twig_Node_Expression_Constant('layout.twig', 0);
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, $filename);
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
$tests[] = array($node, <<<EOF
<?php
@@ -156,7 +156,7 @@ EOF
0
);
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, $filename);
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
$tests[] = array($node, <<<EOF
<?php
+3 -3
View File
@@ -24,7 +24,7 @@ class Twig_Tests_Node_SandboxedModuleTest extends Twig_Tests_Node_TestCase
$macros = new Twig_Node();
$traits = new Twig_Node();
$filename = 'foo.twig';
$node = new Twig_Node_Module($body, $parent, $blocks, $macros, $traits, $filename);
$node = new Twig_Node_Module($body, $parent, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
$node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle'));
$this->assertEquals($body, $node->getNode('body'));
@@ -58,7 +58,7 @@ class Twig_Tests_Node_SandboxedModuleTest extends Twig_Tests_Node_TestCase
$traits = new Twig_Node();
$filename = 'foo.twig';
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, $filename);
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
$node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle'));
$tests[] = array($node, <<<EOF
@@ -111,7 +111,7 @@ EOF
$traits = new Twig_Node();
$filename = 'foo.twig';
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, $filename);
$node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
$node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle'));
$tests[] = array($node, <<<EOF