How to retrieve all Variables from a Twig Template?

做~自己de王妃 提交于 2019-11-27 05:24:32

问题


Is it possible to retrieve all variables inside a Twig template with PHP?

Example 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".

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.

I want to set default values to not-set variables and therefore I need a list of all variables that exist inside the template…


回答1:


UPDATE 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). In this case, try {{ dump(_context|keys) }} to get a list of the defined variables by name without dumping their contents.

UPDATE 2017

It is possible by using {{ dump() }} filter. 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. It will check if variable is defined and is not empty, and if no - will replace it with your value.




回答2:


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




回答3:


Answer added at 2015

In the past it wasn't possible. But since version 1.5 dump() function has added. So you can get all variables from current context calling dump() without any parameters:

<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 = 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.

  • Twig function

EDIT:

One can also reference all variables passed to a template (outside the context of dump()), using the global variable _context. 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.

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.




回答4:


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




回答5:


If you need all Twig elements inside of a text, just use:

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. 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);



回答6:


The way I do it is

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

And then I just check my console using DevTools




回答7:


After using duncan's answer for quite some time I finally found the "right" way to dump all twig variables of a template:

{% 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() }}.

if you put the contents of dump() into a variable:

{% set d = dump() %}

you 'll get all the variables but in "dump ready" html so it would be a pain to parse it.

Hope that helps.




回答8:


I think 19Gerhard85's answer is pretty good, although it might need some tweaking because it matched some empty strings for me. I like using existing functions where possible and this is an approach mostly using twig's functions. 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
        }
    }
}



回答9:


$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 :-)




回答10:


You have to parse the template, and walk through the AST it returns:

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



回答11:


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,}}{{. 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.

/**
 * 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 :)




回答12:


Create a Twig_Extension and add a function with the needs_context flag:

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:

{{myTwigFunction()}}

If you need assistance on creating a Twig Extension, please refer to this documentation:

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




回答13:


This Question has a douplicate – there I found a useful and kind of more powerfull RegEX than above. 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.



回答14:


I built a Twig2Schema class to infer variables from a 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.

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.

To use it, you can either call infer for the whole template, or a subset of the tree using 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;
    }
}


来源:https://stackoverflow.com/questions/12799094/how-to-retrieve-all-variables-from-a-twig-template

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!