How to retrieve all Variables from a Twig Template?

PhpSymfonyTwigTemplate Engine

Php Problem Overview


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…

Php Solutions


Solution 1 - Php

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

Solution 2 - Php

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.

Solution 3 - Php

The way I do it is

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

And then I just check my console using DevTools

Solution 4 - Php

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.

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.

Solution 5 - Php

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

Solution 6 - Php

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

Solution 7 - Php

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

Solution 8 - Php

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.

Solution 9 - Php

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

Solution 10 - Php

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

Solution 11 - Php

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

Solution 12 - Php

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

Solution 13 - Php

If you look at twig compile process you can see that there is parameter called ignore_strict_check if it's true compile will replace missed variables with null but if false compile will throw run time error look at file twig/src/Node/Expression/NameExpression.php line 63 in symfony you can set this parameter via twig package configurations strict_variables: false

Solution 14 - Php

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

Solution 15 - Php

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionStefan NeubigView Question on Stackoverflow
Solution 1 - PhpduncanView Answer on Stackoverflow
Solution 2 - PhpVitalii ZurianView Answer on Stackoverflow
Solution 3 - PhpLuke MadhangaView Answer on Stackoverflow
Solution 4 - PhpfelipsmartinsView Answer on Stackoverflow
Solution 5 - PhpReaperSoonView Answer on Stackoverflow
Solution 6 - Php19Gerhard85View Answer on Stackoverflow
Solution 7 - PhpmickadooView Answer on Stackoverflow
Solution 8 - PhpMawcelView Answer on Stackoverflow
Solution 9 - PhpLarsView Answer on Stackoverflow
Solution 10 - PhpdevicenullView Answer on Stackoverflow
Solution 11 - PhpAlexey AbrahamView Answer on Stackoverflow
Solution 12 - PhpCameron WilbyView Answer on Stackoverflow
Solution 13 - PhpTanogoView Answer on Stackoverflow
Solution 14 - PhpJosé Ricardo JúniorView Answer on Stackoverflow
Solution 15 - PhpmacwinnieView Answer on Stackoverflow