是否可以在 Twig 3 中制作全局宏?
Is it possible to make global macros in Twig 3?
我想将我的 Twig 从非常旧的版本(2.x 甚至 1.x)升级到3.3。在旧版本中,在顶级模板中导入的宏在所有扩展和包含的模板中都可用。我有一堆模板需要我的宏(100 多个和许多 embed
块)。我不想在每个模板中手动导入宏。
所以,根据类似的问题,我尝试实施建议的解决方案,但它不起作用。
其实我试过这个:
$tpl = $this->Twig->load('inc/function.twig');
$this->Twig->addGlobal('fnc', $tpl);
我也试过这个:
$tpl = $this->Twig->load('inc/function.twig');
$this->Twig->addGlobal('fnc', $tpl->unwrap());
但我得到了相同的结果。在模板中定义了 fnc
但它是一个模板对象,我无法访问宏。当我尝试这样做时出现致命错误:
Fatal error: Uncaught Twig\Error\RuntimeError: Accessing \Twig\Template attributes is forbidden
据我了解,在 Twig 3 中,您不能仅使用 addGlobal
.
包含宏
旧的 Twig 已添加到我们的存储库(未被忽略),我们可能也会将新的 Twig 添加到存储库,因此可以修改 Twig 的源代码。
更新:
当我尝试使用宏 addGlobal
我的模板时,我得到
Fatal error: Uncaught LogicException: Unable to add global "fnc" as the runtime or the extensions have already been initialized.
我已经使用 this solution 解决了这个问题(我扩展了 Environment
class)。
在一些测试中,我发现您仍然可以使用纯 PHP
调用宏中定义的“函数”
<?php
$wrapper = $twig->load('macro.html');
$template = $wrapper->unwrap();
echo $template->macro_Foo().''; //return a \Twig\Markup
有了这个,您可以围绕宏编写一个包装器,并尝试将它们自动加载到容器中。
首先我们需要一个扩展来启用和访问容器
<?php
class MacroWrapperExtension extends \Twig\Extension\AbstractExtension
{
public function getFunctions()
{
return [
new \Twig\TwigFunction('macro', [$this, 'macro'], ['needs_environment' => true,]),
];
}
protected $container = null;
public function macro(\Twig\Environment $twig, $template) {
return $this->getContainer($twig)->get($template);
}
private function getContainer(\Twig\Environment $twig) {
if ($this->container === null) $this->container = new MacroWrapperContainer($twig);
return $this->container;
}
}
接下来是容器本身。容器负责加载和 store/save 内存中的(自动加载)宏。该代码将尝试在您的视图文件夹中定位并加载地图宏中的任何文件。
template
|--- index.html
|--- macros
|------- test.html
|
<?php
class MacroWrapperContainer {
const FOLDER = 'macros';
protected $twig = null;
protected $macros = [];
public function __construct(\Twig\Environment $twig) {
$this->setTwig($twig)
->load();
}
public function get($macro) {
return $this->macros[$macro] ?? null;
}
protected function load() {
foreach($this->getTwig()->getLoader()->getPaths() as $path) {
if (!is_dir($path.'/'.self::FOLDER)) continue;
$this->loadMacros($path.'/'.self::FOLDER);
}
}
protected function loadMacros($path) {
$files = scandir($path);
foreach($files as $file) if ($this->isTemplate($file)) $this->loadMacro($file);
}
protected function loadMacro($file) {
$name = pathinfo($file, PATHINFO_FILENAME);
if (!isset($this->macros[$name])) $this->macros[$name] = new MacroWrapper($this->getTwig()->load(self::FOLDER.'/'.$file));
}
protected function isTemplate($file) {
return in_array(pathinfo($file, PATHINFO_EXTENSION), [ 'html', 'twig', ]);
}
protected function setTwig(\Twig\Environment $twig) {
$this->twig = $twig;
return $this;
}
protected function getTwig() {
return $this->twig;
}
public function __call($method_name, $args) {
return $this->get($method_name);
}
}
最后我们需要模仿我在问题开头发布的行为。因此,让我们围绕宏模板创建一个包装器,它将负责调用宏内的实际函数。
正如所见,宏中的函数以 macro_
为前缀,因此只需让 auto-prefix 对宏包装器的每次调用都使用 macro_
<?php
class MacroWrapper {
protected $template = null;
public function __construct(\Twig\TemplateWrapper $template_wrapper) {
$this->template = $template_wrapper->unwrap();
}
public function __call($method_name, $args){
return $this->template->{'macro_'.$method_name}(...$args);
}
}
现在将扩展注入 twig
$twig->addExtension(new MacroWrapperExtension());
这将在每个模板中启用功能 macro
,这让我们可以访问宏文件夹中的任何宏文件
{{ macro('test').hello('foo') }}
{{ macro('test').bar('foo', 'bar', 'foobar') }}
我想将我的 Twig 从非常旧的版本(2.x 甚至 1.x)升级到3.3。在旧版本中,在顶级模板中导入的宏在所有扩展和包含的模板中都可用。我有一堆模板需要我的宏(100 多个和许多 embed
块)。我不想在每个模板中手动导入宏。
所以,根据类似的问题
$tpl = $this->Twig->load('inc/function.twig');
$this->Twig->addGlobal('fnc', $tpl);
我也试过这个:
$tpl = $this->Twig->load('inc/function.twig');
$this->Twig->addGlobal('fnc', $tpl->unwrap());
但我得到了相同的结果。在模板中定义了 fnc
但它是一个模板对象,我无法访问宏。当我尝试这样做时出现致命错误:
Fatal error: Uncaught Twig\Error\RuntimeError: Accessing \Twig\Template attributes is forbidden
据我了解,在 Twig 3 中,您不能仅使用 addGlobal
.
旧的 Twig 已添加到我们的存储库(未被忽略),我们可能也会将新的 Twig 添加到存储库,因此可以修改 Twig 的源代码。
更新:
当我尝试使用宏 addGlobal
我的模板时,我得到
Fatal error: Uncaught LogicException: Unable to add global "fnc" as the runtime or the extensions have already been initialized.
我已经使用 this solution 解决了这个问题(我扩展了 Environment
class)。
在一些测试中,我发现您仍然可以使用纯 PHP
<?php
$wrapper = $twig->load('macro.html');
$template = $wrapper->unwrap();
echo $template->macro_Foo().''; //return a \Twig\Markup
有了这个,您可以围绕宏编写一个包装器,并尝试将它们自动加载到容器中。
首先我们需要一个扩展来启用和访问容器
<?php
class MacroWrapperExtension extends \Twig\Extension\AbstractExtension
{
public function getFunctions()
{
return [
new \Twig\TwigFunction('macro', [$this, 'macro'], ['needs_environment' => true,]),
];
}
protected $container = null;
public function macro(\Twig\Environment $twig, $template) {
return $this->getContainer($twig)->get($template);
}
private function getContainer(\Twig\Environment $twig) {
if ($this->container === null) $this->container = new MacroWrapperContainer($twig);
return $this->container;
}
}
接下来是容器本身。容器负责加载和 store/save 内存中的(自动加载)宏。该代码将尝试在您的视图文件夹中定位并加载地图宏中的任何文件。
template
|--- index.html
|--- macros
|------- test.html
|
<?php
class MacroWrapperContainer {
const FOLDER = 'macros';
protected $twig = null;
protected $macros = [];
public function __construct(\Twig\Environment $twig) {
$this->setTwig($twig)
->load();
}
public function get($macro) {
return $this->macros[$macro] ?? null;
}
protected function load() {
foreach($this->getTwig()->getLoader()->getPaths() as $path) {
if (!is_dir($path.'/'.self::FOLDER)) continue;
$this->loadMacros($path.'/'.self::FOLDER);
}
}
protected function loadMacros($path) {
$files = scandir($path);
foreach($files as $file) if ($this->isTemplate($file)) $this->loadMacro($file);
}
protected function loadMacro($file) {
$name = pathinfo($file, PATHINFO_FILENAME);
if (!isset($this->macros[$name])) $this->macros[$name] = new MacroWrapper($this->getTwig()->load(self::FOLDER.'/'.$file));
}
protected function isTemplate($file) {
return in_array(pathinfo($file, PATHINFO_EXTENSION), [ 'html', 'twig', ]);
}
protected function setTwig(\Twig\Environment $twig) {
$this->twig = $twig;
return $this;
}
protected function getTwig() {
return $this->twig;
}
public function __call($method_name, $args) {
return $this->get($method_name);
}
}
最后我们需要模仿我在问题开头发布的行为。因此,让我们围绕宏模板创建一个包装器,它将负责调用宏内的实际函数。
正如所见,宏中的函数以 macro_
为前缀,因此只需让 auto-prefix 对宏包装器的每次调用都使用 macro_
<?php
class MacroWrapper {
protected $template = null;
public function __construct(\Twig\TemplateWrapper $template_wrapper) {
$this->template = $template_wrapper->unwrap();
}
public function __call($method_name, $args){
return $this->template->{'macro_'.$method_name}(...$args);
}
}
现在将扩展注入 twig
$twig->addExtension(new MacroWrapperExtension());
这将在每个模板中启用功能 macro
,这让我们可以访问宏文件夹中的任何宏文件
{{ macro('test').hello('foo') }}
{{ macro('test').bar('foo', 'bar', 'foobar') }}