ブロック方式
この方法は、GitHubでSebastiaan Stokによって提案されました。
ブロック機能を利用したアイデアです。指定されたブロックの内容を書き込み、複数回呼び出すことができます。
ラッパーファイル:
{# src/Fuz/LayoutBundle/Resources/views/Default/wrappers.html.twig #}
{% block box_head %}
<div class="box indent">
<div class="padding">
{% enblock %}
{% block box_foot %}
</div>
</div>
{% enblock %}
特集ページ:
{{ block('box_head') }}
Some content
{{ block('box_foot') }}
マクロを使用したラップ拡張
このアイデアは、 CharlesによってGitHubで提案されました。
まず、ファイルでマクロを宣言しmacro.html.twig
ます。
{% macro box(content) %}
<div class="box indent">
<div class="padding">
{{ content | raw }}
</div>
</div>
{% endmacro %}
Amd を呼び出す代わりに(ドキュメント{{ macros.box('my content') }}
を参照して、マクロ呼び出しを処理するタグを作成します。{% wrap %}
[% wrap %}
{% endwrap %}
この拡張機能は簡単に開発できました。マクロにアクセスするのは難しいのではないかと思っていましたが、実際にはオブジェクトとしてコンテキストに格納されており、呼び出しは簡単にコンパイルできます。
いくつかの変更のみ: 次の構文を使用します。
{# to access a macro from an object #}
{% wrap macro_object macro_name %}
my content here
{% endwrap %}
{# to access a macro declared in the same file #}
{% wrap macro_name %}
macro
{% endwrap %}
次のコードでは、機能させたい場合は名前空間を変更することを忘れないでください!
まず、services.yml に拡張機能を追加します。
parameters:
fuz_tools.twig.wrap_extension.class: Fuz\ToolsBundle\Twig\Extension\WrapExtension
services:
fuz_tools.twig.wrap_extension:
class: '%fuz_tools.twig.wrap_extension.class%'
tags:
- { name: twig.extension }
バンドル内に Twig ディレクトリを作成します。
拡張機能を追加すると、新しいタグが返されますTokenParser
(英語では、新しいタグが宣言されます)。
小枝/拡張機能/WrapExtension.php:
<?php
// src/Fuz/ToolsBundle/Twig/Extension/WrapExtension.php
namespace Fuz\ToolsBundle\Twig\Extension;
use Fuz\ToolsBundle\Twig\TokenParser\WrapHeaderTokenParser;
use Fuz\ToolsBundle\Twig\TokenParser\WrapFooterTokenParser;
use Fuz\ToolsBundle\Twig\TokenParser\WrapTokenParser;
class WrapExtension extends \Twig_Extension
{
public function getTokenParsers()
{
return array (
new WrapTokenParser(),
);
}
public function getName()
{
return 'wrap';
}
}
{% wrap %}
次に、TokenParser 自体を追加します。これは、パーサーがタグを見つけたときに満たされます。これTokenParser
は、タグが正しく呼び出されているかどうかを確認し (この例では、2 つのパラメーターがあります)、それらのパラメーターを保存し{% wrap %}
、{% endwrap %}` の間のコンテンツを取得します。
小枝/TokenParser/WrapTokenParser.php:
<?php
// src/Fuz/ToolsBundle/Twig/TokenParser/WrapTokenParser.php
namespace Fuz\ToolsBundle\Twig\TokenParser;
use Fuz\ToolsBundle\Twig\Node\WrapNode;
class WrapTokenParser extends \Twig_TokenParser
{
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$object = null;
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
if ($stream->test(\Twig_Token::BLOCK_END_TYPE))
{
if (!$this->parser->hasMacro($name))
{
throw new \Twig_Error_Syntax("The macro '$name' does not exist", $lineno);
}
}
else
{
$object = $name;
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
}
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array ($this, 'decideWrapEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new WrapNode($object, $name, $body, $token->getLine(), $this->getTag());
}
public function decideWrapEnd(\Twig_Token $token)
{
return $token->test('endwrap');
}
public function getTag()
{
return 'wrap';
}
}
{% wrap %}
次に、コンパイラ (twig 方言のノード) が必要です。コンパイラは、タグに関連付けられた PHP コードを生成します。
このタグは のエイリアスで{{ macro_object.box(content) }}
あるため、その行をテンプレートに記述し、生成された php ファイル (ディレクトリに保存されている) で結果のコードを確認しましたapp/cache/dev/twig
。私が得た:
echo $this->getAttribute($this->getContext($context, "(macro object name)"), "(name)", array("(body)"), "method");
だから私のコンパイラは次のようになりました:
小枝/ノード/WrapNode.php:
<?php
// src/Fuz/ToolsBundle/Twig/Node/WrapNode.php
namespace Fuz\ToolsBundle\Twig\Node;
class WrapNode extends \Twig_Node
{
public function __construct($object, $name, $body, $lineno = 0, $tag = null)
{
parent::__construct(array ('body' => $body), array ('object' => $object, 'name' => $name), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('ob_start();');
$compiler
->addDebugInfo($this)
->subcompile($this->getNode('body'));
if (is_null($this->getAttribute('object')))
{
$compiler
->write(sprintf('echo $this->get%s(ob_get_clean());', $this->getAttribute('name')) . "\n");
}
else
{
$compiler
->write('echo $this->getAttribute($this->getContext($context, ')
->repr($this->getAttribute('object'))
->raw('), ')
->repr($this->getAttribute('name'))
->raw(', array(ob_get_clean()), "method");')
->raw("\n");
}
}
}
注:サブパース/サブコンパイルがどのように機能するかを知るために、spaceless
拡張ソースコードを読みました。
それで全部です!大きなボディを持つマクロを使用できるようにするエイリアスを取得します。それを試すには:
macros.html.twig:
{% macro box(content) %}
<div class="box indent">
<div class="padding">
{{ content | raw }} {# Don't forget the raw filter! #}
</div>
</div>
{% endmacro %}
いくつかのlayout.html.twig:
{% import "FuzLayoutBundle:Default:macros.html.twig" as macros %}
{% wrap macros box %}
test
{% endwrap %}
{% macro test(content) %}
some {{ content | raw }} in the same file
{% endmacro %}
{% wrap test %}
macro
{% endwrap %}
出力:
<div class="box indent">
<div class="padding">
test
</div>
</div>
some macro in the same file
wrapperheader、wrapperfooter、wrapper エクステンション
この方法は、私の質問で説明した方法です。トークンパーサーで自分自身をトレーニングしたい場合は、それを読んだり実装したりできますが、機能的には、以前の方法よりも良くありません。
ファイルで、すべてのラッパーを宣言しwrapper.html.twig
ます。
{% wrapperheader box %}
<div class="box">
{% endwrapper %}
{% wrapperfooter box %}
</div>
{% endwrapperfooter %}
features twig ファイルでは、ラッパーを使用します。
{% wrapper box %}
This is my content
{% endwrapper %}
次の拡張機能には 3 つの問題があります。
Twig 環境にデータ (コンテキスト変数など) を保存する方法はありません。したがって、 を定義する場合{% wrapperheader NAME %}
、基本的に、 のヘッダーNAME
が既に定義されているかどうかを確認する明確な方法はありません (この拡張機能では、静的プロパティを使用します)。
小枝ファイルの場合include
、すぐにではなく実行時に解析されます (つまり、含まれている小枝テンプレートは、タグが解析されるときではなく、生成されたファイルが実行されるときに解析されます)。そのため、タグinclude
を解析するときに、以前にインクルードされたファイルにラッパーが存在するかどうかを知ることはできません。{% wrapper NAME %}
ラッパーが存在しない場合、この拡張機能はその間{% wrapper %}
にあるもの{% endwrapper %}
を予告なしに表示するだけです。
この拡張の考え方は次のとおりです。パーサーが and タグに遭遇するwrapperheader
とwrapperfooter
、コンパイラはタグのコンテンツをどこかに保存して、後でwrapper
タグで使用できるようにします。{% include %}
ただし、小枝のコンテキストは、参照ではなくコピーとして渡されます。そのため、上位レベル (ファイルを含むファイル内) で使用するために、そのコンテキスト内に情報{% wrapperheader %}
を格納することはできません。{% wrapperfooter %}
グローバル コンテキストも使用する必要がありました。
これがコードです。名前空間を変更するように注意してください。
まず、Twig に新しいトークン パーサーを追加する拡張機能を作成する必要があります。
バンドルの services.yml 内に、次の行を追加して拡張機能を有効にします。
parameters:
fuz_tools.twig.wrapper_extension.class: Fuz\ToolsBundle\Twig\Extension\WrapperExtension
services:
fuz_tools.twig.wrapper_extension:
class: '%fuz_tools.twig.wrapper_extension.class%'
tags:
- { name: twig.extension }
バンドル内に Twig ディレクトリを作成します。
次の Twig\Extension\WrapperExtension.php ファイルを作成します。
<?php
// src/Fuz/ToolsBundle/Twig/Extension/WrapperExtension.php
namespace Fuz\ToolsBundle\Twig\Extension;
use Fuz\ToolsBundle\Twig\TokenParser\WrapperHeaderTokenParser;
use Fuz\ToolsBundle\Twig\TokenParser\WrapperFooterTokenParser;
use Fuz\ToolsBundle\Twig\TokenParser\WrapperTokenParser;
class WrapperExtension extends \Twig_Extension
{
public function getTokenParsers()
{
return array(
new WrapperHeaderTokenParser(),
new WrapperFooterTokenParser(),
new WrapperTokenParser(),
);
}
public function getName()
{
return 'wrapper';
}
}
次に、トークン パーサーを追加する必要があります。構文はand{% wrapper NAME %} ... {% endwrapper %}
と同じです。したがって、これらのトークン パーサーは、タグの宣言、ラッパーの の取得、および本体 (と endwrapper` の間にあるもの) の取得に使用されます。wrapperheader
wrapperfooter
NAME
wrapper
ラッパーのトークン パーサー: Twig\TokenParser\WrapperTokenParser.php:
<?php
// src/Fuz/ToolsBundle/Twig/TokenParser/WrapperTokenParser.php
namespace Fuz\ToolsBundle\Twig\TokenParser;
use Fuz\ToolsBundle\Twig\Node\WrapperNode;
class WrapperTokenParser extends \Twig_TokenParser
{
public function parse(\Twig_Token $token)
{
$stream = $this->parser->getStream();
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideWrapperEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new WrapperNode($name, $body, $token->getLine(), $this->getTag());
}
public function decideWrapperEnd(\Twig_Token $token)
{
return $token->test('endwrapper');
}
public function getTag()
{
return 'wrapper';
}
}
wrapperheader のトークン パーサー: Twig\TokenParser\WrapperHeaderTokenParser.php:
<?php
// src/Fuz/ToolsBundle/Twig/TokenParser/WrapperHeaderTokenParser.php
namespace Fuz\ToolsBundle\Twig\TokenParser;
use Fuz\ToolsBundle\Twig\Node\WrapperHeaderNode;
class WrapperHeaderTokenParser extends \Twig_TokenParser
{
static public $wrappers = array ();
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
if (in_array($name, self::$wrappers))
{
throw new \Twig_Error_Syntax("The wrapper '$name''s header has already been defined.", $lineno);
}
self::$wrappers[] = $name;
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideWrapperHeaderEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new WrapperHeaderNode($name, $body, $token->getLine(), $this->getTag());
}
public function decideWrapperHeaderEnd(\Twig_Token $token)
{
return $token->test('endwrapperheader');
}
public function getTag()
{
return 'wrapperheader';
}
}
wrapperfooter のトークン パーサー: Twig\TokenParser\WrapperFooterTokenParser.php:
<?php
// src/Fuz/ToolsBundle/Twig/TokenParser/WrapperFooterTokenParser.php
namespace Fuz\ToolsBundle\Twig\TokenParser;
use Fuz\ToolsBundle\Twig\Node\WrapperFooterNode;
class WrapperFooterTokenParser extends \Twig_TokenParser
{
static public $wrappers = array ();
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
if (in_array($name, self::$wrappers))
{
throw new \Twig_Error_Syntax("The wrapper '$name''s footer has already been defined.", $lineno);
}
self::$wrappers[] = $name;
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideWrapperFooterEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new WrapperFooterNode($name, $body, $token->getLine(), $this->getTag());
}
public function decideWrapperFooterEnd(\Twig_Token $token)
{
return $token->test('endwrapperfooter');
}
public function getTag()
{
return 'wrapperfooter';
}
}
トークン パーサーは必要な情報をすべて取得します。これらの情報を PHP にコンパイルする必要があります。この PHP コードは、Twig_Template 実装内の twig エンジンによって生成されます (生成されたクラスはキャッシュ ディレクトリにあります)。メソッドでコードを生成し、インクルードされたファイルのコンテキストは使用できません (コンテキスト配列が参照によって与えられないため)。このように、グローバル コンテキストがなければ、インクルード ファイルの内容にアクセスすることはできません。そのため、ここでは静的属性を使用しています... まったく良くありませんが、それらを回避する方法がわかりません (アイデアがあれば教えてください! :))。
ラッパータグのコンパイラ: Twig\Nodes\WrapperNode.php
<?php
// src/Fuz/ToolsBundle/Twig/Node/WrapperNode.php
namespace Fuz\ToolsBundle\Twig\Node;
class WrapperNode extends \Twig_Node
{
public function __construct($name, $body, $lineno = 0, $tag = null)
{
parent::__construct(array ('body' => $body), array ('name' => $name), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('if (isset(\\')
->raw(__NAMESPACE__)
->raw('\WrapperHeaderNode::$headers[')
->repr($this->getAttribute('name'))
->raw('])) {')
->raw("\n")
->indent()
->write('echo \\')
->raw(__NAMESPACE__)
->raw('\WrapperHeaderNode::$headers[')
->repr($this->getAttribute('name'))
->raw('];')
->raw("\n")
->outdent()
->write('}')
->raw("\n");
$compiler
->addDebugInfo($this)
->subcompile($this->getNode('body'));
$compiler
->addDebugInfo($this)
->write('if (isset(\\')
->raw(__NAMESPACE__)
->raw('\WrapperFooterNode::$footers[')
->repr($this->getAttribute('name'))
->raw('])) {')
->raw("\n")
->indent()
->write('echo \\')
->raw(__NAMESPACE__)
->raw('\WrapperFooterNode::$footers[')
->repr($this->getAttribute('name'))
->raw('];')
->raw("\n")
->outdent()
->write('}')
->raw("\n");
}
}
wrapperheader タグのコンパイラ: Twig\Nodes\WrapperHeaderNode.php
<?php
// src/Fuz/ToolsBundle/Twig/Node/WrapperHeaderNode.php
namespace Fuz\ToolsBundle\Twig\Node;
/**
* @author alain tiemblo
*/
class WrapperHeaderNode extends \Twig_Node
{
static public $headers = array();
public function __construct($name, $body, $lineno = 0, $tag = null)
{
parent::__construct(array ('body' => $body), array ('name' => $name), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler
->write("ob_start();")
->raw("\n")
->subcompile($this->getNode('body'))
->write(__CLASS__)
->raw('::$headers[')
->repr($this->getAttribute('name'))
->raw('] = ob_get_clean();')
->raw("\n");
}
}
wrapperfooter タグのコンパイラ: Twig\Nodes\WrapperFooterNode.php
<?php
// src/Fuz/ToolsBundle/Twig/Node/WrapperFooterNode.php
namespace Fuz\ToolsBundle\Twig\Node;
class WrapperFooterNode extends \Twig_Node
{
static public $footers = array();
public function __construct($name, $body, $lineno = 0, $tag = null)
{
parent::__construct(array ('body' => $body), array ('name' => $name), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler
->write("ob_start();")
->raw("\n")
->subcompile($this->getNode('body'))
->write(__CLASS__)
->raw('::$footers[')
->repr($this->getAttribute('name'))
->raw('] = ob_get_clean();')
->raw("\n");
}
}
これで実装はOKです。試してみよう!
wrappers.html.twig という名前のビューを作成します。
{# src/Fuz/LayoutBundle/Resources/views/Default/wrappers.html.twig #}
{% wrapperheader demo %}
HEAD
{% endwrapperheader %}
{% wrapperfooter demo %}
FOOT
{% endwrapperfooter %}
what you want.html.twig という名前のビューを作成します。
{# src/Fuz/HomeBundle/Resources/views/Default/index.html.twig #}
{% include 'FuzLayoutBundle:Default:wrappers.html.twig' %}
{% wrapper demo %}
O YEAH
{% endwrapper %}
これが表示されます:
HEAD O YEAH FOOT