Skip to content

Commit

Permalink
feature #2239 Add "is defined" support for block() (hason, fabpot)
Browse files Browse the repository at this point in the history
This PR was merged into the 1.x branch.

Discussion
----------

Add "is defined" support for block()

replaces #1831, fixes #1821

I think reusing the semantic of the `defined` test is more idiomatic and easily discoverable.

/cc @hason

Commits
-------

3ce06af added 'is defined' support for block()
4f013e0 Add test to check if a block exists
  • Loading branch information
fabpot committed Nov 11, 2016
2 parents c577631 + 3ce06af commit 0d6581b
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
9 changes: 9 additions & 0 deletions doc/functions/block.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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>`
28 changes: 18 additions & 10 deletions lib/Twig/Node/Expression/BlockReference.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,32 @@ public function __construct(Twig_NodeInterface $name, $lineno, $tag = null)
$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)')
;
}
}
}
}
3 changes: 2 additions & 1 deletion lib/Twig/Node/Expression/Test/Defined.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface
$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 {
Expand Down
35 changes: 35 additions & 0 deletions lib/Twig/Template.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ public function renderBlock($name, array $context, array $blocks = array(), $use
*
* @return bool true if the block exists, false otherwise
*
* @see blockExists
*
* @internal
*/
public function hasBlock($name)
Expand Down Expand Up @@ -653,4 +655,37 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ

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;
}
}
38 changes: 38 additions & 0 deletions test/Twig/Tests/Fixtures/tests/defined_for_blocks.test
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 0d6581b

Please sign in to comment.