diff --git a/CHANGELOG b/CHANGELOG index 6920bd1fb..1a0807c9f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ * 1.28.0 (2016-XX-XX) + * added "is defined" support for block() * optimized the way attributes are fetched * 1.27.0 (2016-10-25) diff --git a/doc/functions/block.rst b/doc/functions/block.rst index fd571efbc..2f3de2456 100644 --- a/doc/functions/block.rst +++ b/doc/functions/block.rst @@ -12,4 +12,13 @@ times, use the ``block`` function: {% block body %}{% endblock %} +Use the ``defined`` test to check if a block exists in the context of the +current template: + +.. code-block:: jinja + + {% if block("footer") is defined %} + ... + {% endif %} + .. seealso:: :doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>` diff --git a/lib/Twig/Node/Expression/BlockReference.php b/lib/Twig/Node/Expression/BlockReference.php index 3fe7d0580..d22dd57cc 100644 --- a/lib/Twig/Node/Expression/BlockReference.php +++ b/lib/Twig/Node/Expression/BlockReference.php @@ -26,24 +26,32 @@ class Twig_Node_Expression_BlockReference extends Twig_Node_Expression $tag = func_num_args() > 3 ? func_get_arg(3) : null; } - parent::__construct(array('name' => $name), array('output' => false), $lineno, $tag); + parent::__construct(array('name' => $name), array('is_defined_test' => false, 'output' => false), $lineno, $tag); } public function compile(Twig_Compiler $compiler) { - if ($this->getAttribute('output')) { + if ($this->getAttribute('is_defined_test')) { $compiler - ->addDebugInfo($this) - ->write('$this->displayBlock(') - ->subcompile($this->getNode('name')) - ->raw(", \$context, \$blocks);\n") - ; - } else { - $compiler - ->raw('$this->renderBlock(') + ->raw('$this->blockExists(') ->subcompile($this->getNode('name')) ->raw(', $context, $blocks)') ; + } else { + if ($this->getAttribute('output')) { + $compiler + ->addDebugInfo($this) + ->write('$this->displayBlock(') + ->subcompile($this->getNode('name')) + ->raw(", \$context, \$blocks);\n") + ; + } else { + $compiler + ->raw('$this->renderBlock(') + ->subcompile($this->getNode('name')) + ->raw(', $context, $blocks)') + ; + } } } } diff --git a/lib/Twig/Node/Expression/Test/Defined.php b/lib/Twig/Node/Expression/Test/Defined.php index c57fe09eb..a8fd74db9 100644 --- a/lib/Twig/Node/Expression/Test/Defined.php +++ b/lib/Twig/Node/Expression/Test/Defined.php @@ -29,8 +29,9 @@ class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test $node->setAttribute('is_defined_test', true); } elseif ($node instanceof Twig_Node_Expression_GetAttr) { $node->setAttribute('is_defined_test', true); - $this->changeIgnoreStrictCheck($node); + } elseif ($node instanceof Twig_Node_Expression_BlockReference) { + $node->setAttribute('is_defined_test', true); } elseif ($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array) { $node = new Twig_Node_Expression_Constant(true, $node->getTemplateLine()); } else { diff --git a/lib/Twig/Template.php b/lib/Twig/Template.php index b1865b2ee..01e80716f 100644 --- a/lib/Twig/Template.php +++ b/lib/Twig/Template.php @@ -286,6 +286,8 @@ abstract class Twig_Template implements Twig_TemplateInterface * * @return bool true if the block exists, false otherwise * + * @see blockExists + * * @internal */ public function hasBlock($name) @@ -653,4 +655,37 @@ abstract class Twig_Template implements Twig_TemplateInterface return $ret; } + + /** + * Returns whether a block exists or not in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param string $name The block name + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return bool true if the block exists, false otherwise + * + * @see hasBlock + * + * @internal + */ + protected function blockExists($name, array $context, array $blocks = array()) + { + if (isset($blocks[$name])) { + return $blocks[$name][0] instanceof self; + } + + if (isset($this->blocks[$name])) { + return true; + } + + if (false !== $parent = $this->getParent($context)) { + return $parent->blockExists($name, $context); + } + + return false; + } } diff --git a/test/Twig/Tests/Fixtures/tests/defined_for_blocks.test b/test/Twig/Tests/Fixtures/tests/defined_for_blocks.test new file mode 100644 index 000000000..64d7d0431 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tests/defined_for_blocks.test @@ -0,0 +1,38 @@ +--TEST-- +"defined" support for blocks +--TEMPLATE-- +{% extends 'parent' %} +{% block icon %}icon{% endblock %} +{% block body %} + {{ parent() }} + {{ block('foo') is defined ? 'ok' : 'ko' }} + {{ block('footer') is defined ? 'ok' : 'ko' }} + {{ block('icon') is defined ? 'ok' : 'ko' }} + {{ block('block1') is defined ? 'ok' : 'ko' }} + {%- embed 'embed' %} + {% block content %}content{% endblock %} + {% endembed %} +{% endblock %} +{% use 'blocks' %} +--TEMPLATE(parent)-- +{% block body %} + {{ block('icon') is defined ? 'ok' : 'ko' -}} +{% endblock %} +{% block footer %}{% endblock %} +--TEMPLATE(embed)-- +{{ block('icon') is defined ? 'ok' : 'ko' }} +{{ block('content') is defined ? 'ok' : 'ko' }} +{{ block('block1') is defined ? 'ok' : 'ko' }} +--TEMPLATE(blocks)-- +{% block block1 %}{%endblock %} +--DATA-- +return array() +--EXPECT-- +ok + ko + ok + ok + ok +ko +ok +ko