简体   繁体   English

如何从 Twig 模板中检索所有变量?

[英]How to retrieve all Variables from a Twig Template?

Is it possible to retrieve all variables inside a Twig template with PHP?是否可以使用 PHP 检索 Twig 模板中的所有变量?

Example someTemplate.twig.php:示例 someTemplate.twig.php:

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

Now I want to do something like this:现在我想做这样的事情:

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

$variables should now contain "name" and "email". $variables 现在应该包含“name”和“email”。

The reason I want to do this is that I am working on a CMS system where my twig templates and variables are dynamically set by my users and they also fill the variables through an API.我想这样做的原因是我在一个 CMS 系统上工作,我的树枝模板和变量由我的用户动态设置,他们还通过 API 填充变量。

I want to set default values to not-set variables and therefore I need a list of all variables that exist inside the template…我想将默认值设置为未设置的变量,因此我需要模板中存在的所有变量的列表......

This is useful I find to get all the top-level keys available in the current context: 我发现获得当前上下文中所有可用的顶级键非常有用:

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

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

UPDATE 2019 更新2019

Although {{ dump() }} does work, in some circumstances it may result in a "memory exhausted" error from PHP if it generates too much information (for example, due to recursion). 尽管{{ dump() }}确实有效,但是在某些情况下,如果PHP生成的信息过多(例如由于递归),可能会导致PHP出现“内存耗尽”错误。 In this case, try {{ dump(_context|keys) }} to get a list of the defined variables by name without dumping their contents. 在这种情况下,请尝试{{ dump(_context|keys) }}通过名称获取已定义变量的列表,而不转储其内容。

UPDATE 2017 更新2017

It is possible by using {{ dump() }} filter. 使用{{ dump() }}过滤器是可能的。 Thanks for pointing that out in the comments! 感谢您在评论中指出!


OUTDATED 过时的

It is not possible. 这不可能。

You can look for these variable in twig templates and add |default('your_value') filter to them. 您可以在树枝模板中查找这些变量,然后向它们添加|default('your_value')过滤器。 It will check if variable is defined and is not empty, and if no - will replace it with your value. 它将检查变量是否已定义并且不为空,如果不存在,则将其替换为您的值。

Answer added at 2015 答案于2015年添加

In the past it wasn't possible. 过去是不可能的。 But since version 1.5 dump() function has added. 但自1.5版以来,已添加dump()函数。 So you can get all variables from current context calling dump() without any parameters: 因此,您可以从没有调用任何参数的当前上下文中获取所有变量,方法是:dump():

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

However, you must add the Twig_Extension_Debug extension explicitly when creating your Twig environment because dump() isn't available by default: 但是,在创建Twig环境时,必须显式添加Twig_Extension_Debug扩展,因为默认情况下dump()不可用:

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

If you have been using something like Symfony, Silex, etc, dump() is available by default. 如果您使用的是Symfony,Silex等,则默认情况下dump()可用。

EDIT: 编辑:

One can also reference all variables passed to a template (outside the context of dump() ), using the global variable _context . 也可以使用全局变量_context引用传递给模板的所有变量(在dump()上下文之外dump() This is what you were looking for. 这就是您想要的。 It is an array associating all variable names to their values. 它是一个将所有变量名称与其值关联的数组。

You can find some additional info in the Twig documentation. 您可以在Twig文档中找到一些其他信息。

For this specific question however, it would probably be best to gather all of these custom variables you are speaking of, under a same umbrella variable, so that retrieving them would not be a headache. 但是,对于这个特定问题,最好将所有您要说的这些自定义变量收集在一个总括变量下,这样检索它们就不会太麻烦了。 I would be an array called custom_variables or whatever. 我将是一个名为custom_variables的数组。

Here is the best way and easiest way to dump all variables : 这是转储所有变量的最佳方法和最简单的方法:

{{ dump () }}

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

If you need all Twig elements inside of a text, just use: 如果需要在文本中包含所有Twig元素,请使用:

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

I had a issue where the WSIWYG editor placed HTML tags inside of Twig variables. 我遇到一个问题,其中WSIWYG编辑器将​​HTML标记放置在Twig变量内。 I filter them with: 我用它们过滤:

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;
}

UPDATE 更新

Use this expression to find all {{ }} and {% %} 使用此表达式查找所有{{}}和{%%}

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

The way I do it is 我的方式是

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

And then I just check my console using DevTools 然后我只是使用DevTools检查控制台

After using duncan's answer for quite some time I finally found the "right" way to dump all twig variables of a template: 在使用duncan的答案一段时间后,我终于找到了转储模板中所有树枝变量的“正确”方法:

{% dump %}

That's it. 而已。 All the variables available in the template will be dumped and in the dump section of the profiler, not in the middle of your html like with {{ dump() }} . 模板中所有可用的变量都将被转储到事件探查器的转储部分中,而不是像{{ dump() }}一样出现在html的中间。

if you put the contents of dump() into a variable: 如果将dump()的内容放入变量中:

{% set d = dump() %}

you 'll get all the variables but in "dump ready" html so it would be a pain to parse it. 您将获得所有变量,但是在“ dump ready” html中,因此很难解析它。

Hope that helps. 希望能有所帮助。

I think 19Gerhard85's answer is pretty good, although it might need some tweaking because it matched some empty strings for me. 我认为19Gerhard85的答案相当不错,尽管可能需要进行一些调整,因为它为我匹配了一些空字符串。 I like using existing functions where possible and this is an approach mostly using twig's functions. 我喜欢在可能的情况下使用现有功能,而这是大多数使用twig功能的方法。 You need access to your application's twig environment. 您需要访问应用程序的树枝环境。

/**
 * @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);
}

And the only custom part of it is the recursive function to collect only certain types of nodes: 并且它的唯一自定义部分是仅收集某些类型的节点的递归函数:

/**
 * @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
        }
    }
}
$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;
}

have fun :-) 玩得开心 :-)

You have to parse the template, and walk through the AST it returns: 您必须解析模板,并逐步执行返回的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;
}

After I spent quite a night, trying all the above answers, I realized, for some unexpected reason, regexps did not work at all with my simple templates. 在花了整整一个晚上,尝试以上所有答案后,我意识到,由于某些意外原因,正则表达式根本无法用于我的简单模板。 They returned junk or partial information. 他们返回了垃圾信息或部分信息。 So I decided to go by erasing all the content between tags instead of counting tags ^_^. 因此,我决定删除所有标签之间的内容,而不是对标签^ _ ^进行计数。

I mean, if a template is 'AAA {{BB}} CC {{DD}} {{BB}} SS' , I just add '}}' in the beginning of the template and '{{ in the end.... and all the content between }} and {{ I'll just strip out, adding comma in between => }}{{BB,}}{{DD,}}{{BB,}}{{ . 我的意思是,如果模板是'AAA {{BB}} CC {{DD}} {{BB}} SS' ,我只需在模板的开头添加'}}' ,在末尾添加'{{ 。 ..和}}{{之间的所有内容,我将删除,在=> }}{{BB,}}{{DD,}}{{BB,}}{{之间添加逗号。 Then - just erase }} and {{ . 然后-只需擦除}}{{

It took me about 15 min to write and test.... but with regexps I've spent about 5 hrs with no success. 我花了大约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));

Hope it'll help somebody :) 希望对别人有帮助:)

Create a Twig_Extension and add a function with the needs_context flag: 创建一个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 '';
    }
}

The context will be passed as first parameter to your function, containing all variables. 上下文将作为包含所有变量的第一个参数传递给您的函数。

On your Twig template you just have to call that function: 在Twig模板上,您只需要调用该函数:

{{myTwigFunction()}}

If you need assistance on creating a Twig Extension, please refer to this documentation: 如果您需要有关创建Twig扩展程序的帮助,请参阅以下文档:

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

This Question has a douplicate – there I found a useful and kind of more powerfull RegEX than above. 这个问题有一个重复的部分 –在这里,我发现了一个比上面有用且更强大的RegEX。 This one, I've improved to match more precise: 我对此进行了改进,以使其更加精确:

\{\{(?!%)\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.

I built a Twig2Schema class to infer variables from a Twig AST. 我构建了一个Twig2Schema类,以从Twig AST推断变量。 To get the variables in a document, you need to recursively "walk" through the Twig AST and have rules in place when you encounter certain types of language nodes. 要获取文档中的变量,您需要递归地“遍历” Twig AST,并在遇到某些类型的语言节点时制定适当的规则。

This class extracts variable names from Nodes if they are not always defined, and also grabs variables from the value used in ForLoopNodes and IfStatements. 此类从节点中提取变量名称(如果未始终定义它们),并且还从ForLoopNodes和IfStatements中使用的值中获取变量。

To use it, you can either call infer for the whole template, or a subset of the tree using inferFromAst . 要使用它,您可以为整个模板调用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;
    }
}

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM