50

是否可以使用 PHP 检索 Twig 模板中的所有变量?

示例 someTemplate.twig.php:

Hello {{ name }}, 
your new email is {{ email }}

现在我想做这样的事情:

$template = $twig->loadTemplate('someTemplate');
$variables = $template->getVariables();

$variables 现在应该包含“name”和“email”。

我想这样做的原因是我正在开发一个 CMS 系统,其中我的树枝模板和变量由我的用户动态设置,并且它们还通过 API 填充变量。

我想将默认值设置为未设置的变量,因此我需要模板中存在的所有变量的列表……</p>

4

15 回答 15

77

我发现这对于获取当前上下文中可用的所有顶级键很有用:

<ol>
    {% for key, value in _context  %}
      <li>{{ key }}</li>
    {% endfor %}
</ol>

感谢https://www.drupal.org/node/1906780

于 2015-10-22T08:39:39.077 回答
51

2019 年更新

虽然{{ dump() }}确实有效,但在某些情况下,如果它生成太多信息(例如,由于递归),它可能会导致 PHP 出现“内存耗尽”错误。在这种情况下,尝试{{ dump(_context|keys) }}按名称获取已定义变量的列表,而不转储其内容。

2017 年更新

可以通过使用{{ dump() }}过滤器。感谢您在评论中指出这一点!


过时的

这不可能。

您可以在 twig 模板中查找这些变量|default('your_value')并向它们添加过滤器。它将检查变量是否已定义并且不为空,如果没有 - 将用您的值替换它。

于 2012-10-09T11:34:37.757 回答
18

我这样做的方式是

<script>console.log({{ _context | json_encode | raw }});</script>

然后我只是使用 DevTools 检查我的控制台

于 2017-05-25T13:59:58.610 回答
16

2015年添加的答案

过去是不可能的。但是从 1.5 版开始添加了dump()函数。因此,您可以从当前上下文调用 dump() 获取所有变量,而无需任何参数:

<pre>
    {{ dump(user) }}
</pre>

但是,您必须在创建 Twig 环境时显式添加Twig_Extension_Debugdump()扩展,因为默认情况下不可用:

$twig = new Twig_Environment($loader, array(
    'debug' => true,
    // ...
));
$twig->addExtension(new Twig_Extension_Debug());

如果你一直在使用 Symfony、Silex 等,dump()默认情况下是可用的。

编辑:

dump()还可以使用全局变量引用传递给模板的所有变量(在 的上下文之外) _context。这就是你要找的。它是一个将所有变量名与其值相关联的数组。

您可以在 Twig 文档中找到一些附加信息。

但是,对于这个特定问题,最好将您所说的所有这些自定义变量收集在同一个伞形变量下,这样检索它们就不会令人头疼。我将是一个名为custom_variables或其他的数组。

于 2015-08-25T12:54:52.480 回答
13

这是转储所有变量的最佳方法和最简单的方法:

{{ dump () }}

来源:https ://www.drupal.org/docs/8/theming/twig/discovering-and-inspecting-variables-in-twig-templates

于 2017-08-08T09:10:38.443 回答
10

如果您需要文本中的所有 Twig 元素,只需使用:

preg_match_all('/\{\%\s*(.*)\s*\%\}|\{\{(?!%)\s*((?:[^\s])*)\s*(?<!%)\}\}/i', $text, $matches);

我遇到了 WSIWYG 编辑器将 HTML 标记放置在 Twig 变量中的问题。我过滤它们:

public function cleanHTML($text)
{
    preg_match_all('/\{\%\s*(.*)\s*\%\}|\{\{(?!%)\s*((?:[^\s])*)\s*(?<!%)\}\}/i', $text, $matches);

    if (isset($matches[0]) && count($matches[0])) {
        foreach ($matches[0] as $match) {
            $clean_match = strip_tags($match);

            $text = str_replace($match, $clean_match, $text);
        }
    }

    return $text;
}

更新

使用此表达式查找所有 {{ }} 和 {% %}

preg_match_all('/\{\%\s*([^\%\}]*)\s*\%\}|\{\{\s*([^\}\}]*)\s*\}\}/i', $text, $matches);
于 2013-07-30T13:25:55.070 回答
5

我认为 19Gerhard85 的答案非常好,尽管它可能需要一些调整,因为它为我匹配了一些空字符串。我喜欢尽可能使用现有函数,这是一种主要使用 twig 函数的方法。您需要访问应用程序的 twig 环境。

/**
 * @param $twigTemplateName
 * @return array
 */
public function getRequiredKeys($twigTemplateName)
{
    $twig = $this->twig;
    $source = $twig->getLoader()->getSource($twigTemplateName);
    $tokens = $twig->tokenize($source);
    $parsed = $twig->getParser()->parse($tokens);
    $collected = [];
    $this->collectNodes($parsed, $collected);

    return array_keys($collected);
}

它唯一的自定义部分是仅收集某些类型节点的递归函数:

/**
 * @param \Twig_Node[] $nodes
 * @param array $collected
 */
private function collectNodes($nodes, array &$collected)
{
    foreach ($nodes as $node) {
        $childNodes = $node->getIterator()->getArrayCopy();
        if (!empty($childNodes)) {
            $this->collectNodes($childNodes, $collected); // recursion
        } elseif ($node instanceof \Twig_Node_Expression_Name) {
            $name = $node->getAttribute('name');
            $collected[$name] = $node; // ensure unique values
        }
    }
}
于 2016-10-18T09:54:35.717 回答
5

在使用邓肯的答案很长一段时间后,我终于找到了转储模板所有树枝变量的“正确”方法:

{% dump %}

而已。模板中可用的所有变量都将被转储并在分析器的转储部分中,而不是像{{ dump() }}.

如果将 的内容dump()放入变量中:

{% set d = dump() %}

您将获得所有变量,但在“转储就绪”html 中,因此解析它会很痛苦。

希望有帮助。

于 2016-12-07T14:48:50.737 回答
3
$loader1 = new Twig_Loader_Array([
    'blub.html' => '{{ twig.template.code }}',
]);
$twig = new Twig_Environment($loader1);
$tokens = $twig->tokenize($loader1->getSource('blub.html'));
$nodes = $twig->getParser()->parse($tokens);

var_dump($this->getTwigVariableNames($nodes));


function getTwigVariableNames($nodes): array
{
    $variables = [];
    foreach ($nodes as $node) {
        if ($node instanceof \Twig_Node_Expression_Name) {
            $name = $node->getAttribute('name');
            $variables[$name] = $name;
        } elseif ($node instanceof \Twig_Node_Expression_Constant && $nodes instanceof \Twig_Node_Expression_GetAttr) {
            $value = $node->getAttribute('value');
            if (!empty($value) && is_string($value)) {
                $variables[$value] = $value;
            }
        } elseif ($node instanceof \Twig_Node_Expression_GetAttr) {
            $path = implode('.', $this->getTwigVariableNames($node));
            if (!empty($path)) {
                $variables[$path] = $path;
            }
        } elseif ($node instanceof \Twig_Node) {
            $variables += $this->getTwigVariableNames($node);
        }
    }
    return $variables;
}

玩得开心 :-)

于 2018-03-09T22:47:00.757 回答
2

您必须解析模板,并遍历它返回的 AST:

$loaded = $twig->getLoader()->getSource($template);
var_dump(extractVars($twig->parse($twig->tokenize($loaded))));

function extractVars($node)
{
    if (!$node instanceof Traversable) return array();

    $vars = array();
    foreach ($node as $cur)
    {
        if (get_class($cur) != 'Twig_Node_Expression_Name')
        {
            $vars = array_merge($vars, call_user_func(__FUNCTION__, $cur));
        }
        else if ($cur->getAttribute('always_defined') == false)
        {
            // List only predefined variables expected by template, 
            // filtering out `v` and leaving `arr` from `{% for v in arr%}`
            $vars[] = $cur->getAttribute('name');
        }
    }

    return $vars;
}
于 2016-08-10T17:34:19.360 回答
1

我构建了一个 Twig2Schema 类来从 Twig AST 推断变量。要获取文档中的变量,您需要递归地“遍历” Twig AST,并在遇到某些类型的语言节点时制定规则。

此类从节点中提取变量名(如果它们未始终定义),并且还从 ForLoopNodes 和 IfStatements 中使用的值中获取变量。

要使用它,您可以调用infer整个模板,也可以使用inferFromAst.

<?php

class Twig2Schema
{
    /**
     * @param \Twig\Environment $twig - A twig environment containing loaded templates
     * @param $twigTemplateName - The name of the template to infer variables from
     * @param $config - A configuration object for this function
     * @return array
     */
    public function infer(\Twig\Environment $twig, $twigTemplateName)
    {
        $source = $twig->getLoader()->getSourceContext($twigTemplateName);
        $tokens = $twig->tokenize($source);
        $ast = $twig->parse($tokens);
        return $this->inferFromAst($ast);
    }

    /**
     * @param \Twig\Node\ModuleNode $ast - An abstract syntax tree parsed from Twig
     * @return array - The variables used in the Twig template
     */
    public function inferFromAst(\Twig\Node\ModuleNode $ast)
    {
        $keys = $this->visit($ast);

        foreach ($keys as $key => $value) {
            if ($value['always_defined'] || $key === '_self') {
                unset($keys[$key]);
            }
        }

        return $keys;
    }

    /**
     * @param \Twig\Node\Node $ast - The tree to traverse and extract variables
     * @return array - The variables found in this tree
     */
    private function visit(\Twig\Node\Node $ast)
    {
        $vars = [];
        switch (get_class($ast)) {
            case \Twig\Node\Expression\AssignNameExpression::class:
            case \Twig\Node\Expression\NameExpression::class:
                $vars[$ast->getAttribute('name')] = [
                    'type' => get_class($ast),
                    'always_defined' => $ast->getAttribute('always_defined'),
                    'is_defined_test' => $ast->getAttribute('is_defined_test'),
                    'ignore_strict_check' => $ast->getAttribute('ignore_strict_check')
                ];
                break;
            case \Twig\Node\ForNode::class:
                foreach ($ast as $key => $node) {
                    switch ($key) {
                        case 'value_target':
                            $vars[$node->getAttribute('name')] = [
                                'for_loop_target' => true,
                                'always_defined' => $node->getAttribute('always_defined')
                            ];
                            break;
                        case 'body':
                            $vars = array_merge($vars, $this->visit($node));
                            break;
                        default:
                            break;
                    }
                }
                break;
            case \Twig\Node\IfNode::class:
                foreach ($ast->getNode('tests') as $key => $test) {
                    $vars = array_merge($vars, $this->visit($test));
                }
                foreach ($ast->getNode('else') as $key => $else) {
                    $vars = array_merge($vars, $this->visit($else));
                }
                break;
            default:
                if ($ast->count()) {
                    foreach ($ast as $key => $node) {
                        $vars = array_merge($vars, $this->visit($node));
                    }
                }
                break;
        }
        return $vars;
    }
}
于 2019-09-02T13:45:16.883 回答
1

在我花了整整一夜,尝试了上述所有答案之后,我意识到,出于某种意想不到的原因,正则表达式根本不适用于我的简单模板。他们返回垃圾或部分信息。所以我决定删除标签之间的所有内容,而不是计算标签^_^。

我的意思是,如果模板是'AAA {{BB}} CC {{DD}} {{BB}} SS',我只需'}}'在模板的开头和'{{结尾添加.... 和之间的所有内容}}{{我将删除,在 => 之间添加逗号}}{{BB,}}{{DD,}}{{BB,}}{{。然后 - 只需擦除}}{{

我花了大约 15 分钟来编写和测试....但是使用正则表达式我花了大约 5 个小时没有成功。

/**
 * deletes ALL the string contents between all the designated characters
 * @param $start - pattern start 
 * @param $end   - pattern end
 * @param $string - input string, 
 * @return mixed - string
 */
 function auxDeleteAllBetween($start, $end, $string) {
    // it helps to assembte comma dilimited strings
    $string = strtr($start. $string . $end, array($start => ','.$start, $end => chr(2)));
    $startPos  = 0;
    $endPos = strlen($string);
    while( $startPos !== false && $endPos !== false){
        $startPos = strpos($string, $start);
        $endPos = strpos($string, $end);
        if ($startPos === false || $endPos === false) {
            return $string;
        }
        $textToDelete = substr($string, $startPos, ($endPos + strlen($end)) - $startPos);
        $string = str_replace($textToDelete, '', $string);
    }
    return $string;
}

/**
 * This function is intended to replace
 * //preg_match_all('/\{\%\s*([^\%\}]*)\s*\%\}|\{\{\s*([^\}\}]*)\s*\}\}/i', 
 * which did not give intended results for some reason.
 *
 * @param $inputTpl
 * @return array
 */
private function auxGetAllTags($inputTpl){
   $inputTpl = strtr($inputTpl, array('}}' => ','.chr(1), '{{' => chr(2)));
   return explode(',',$this->auxDeleteAllBetween(chr(1),chr(2),$inputTpl));
}


$template = '<style>
td{border-bottom:1px solid #eee;}</style>
<p>Dear {{jedi}},<br>New {{padawan}} is waiting for your approval: </p>
<table border="0">
<tbody><tr><td><strong>Register as</strong></td><td>{{register_as}}, user-{{level}}</td></tr>
<tr><td><strong>Name</strong></td><td>{{first_name}} {{last_name}}</td></tr>...';

print_r($this->auxGetAllTags($template));

希望它会帮助某人:)

于 2016-05-23T00:16:13.557 回答
0

如果您查看 twig 编译过程,您会看到有一个名为 ignore_strict_check 的参数是否为 true 编译将用 null 替换错过的变量,但如果为 false 编译将引发运行时错误,请查看文件 twig/src/Node/Expression/NameExpression.php 行63 在 symfony 中你可以通过 twig 包配置来设置这个参数 strict_variables: false

于 2021-10-27T15:58:13.170 回答
0

创建一个 Twig_Extension 并添加一个带有needs_context标志的函数:

class MyTwigExtension extends Twig_Extension{
   public function getFunctions()
    {
        return array(
            new \Twig_SimpleFunction('myTwigFunction', array($this, 'myTwigFunction'), array('needs_context' => true)),
        );
    }

    public function myTwigFunction($context)
    {
        var_dump($context);
        return '';
    }
}

上下文将作为第一个参数传递给您的函数,其中包含所有变量。

在您的 Twig 模板上,您只需调用该函数:

{{myTwigFunction()}}

如果您在创建 Twig 扩展方面需要帮助,请参阅此文档:

http://twig.sensiolabs.org/doc/2.x/advanced.html

于 2017-03-22T21:47:49.207 回答
0

这个问题有一个重复- 在那里我发现了一个比上面有用且更强大的 RegEX。这一个,我已经改进以匹配更精确:

\{\{(?!%)\s* # Starts with {{ not followed by % followed by 0 or more spaces
  ((?:(?!\.)[^\s])*?) # Match anything without a point or space in it
  (\|(?:(?!\.)[^\s])*)? # Match filter within variable
\s*(?<!%)\}\} # Ends with 0 or more spaces not followed by % ending with }}
| # Or
\{%\s* # Starts with {% followed by 0 or more spaces
  (?:\s(?!endfor)|(endif)|(else)(\w+))+ # Match the last word which can not be endfor, endif or else
\s*%\} # Ends with 0 or more spaces followed by %}
# Flags: i: case insensitive matching | x: Turn on free-spacing mode to ignore whitespace between regex tokens, and allow # comments.
于 2017-04-10T07:37:38.707 回答