How to retrieve all Variables from a Twig Template

2020-02-02 08:15发布

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…

14条回答
Luminary・发光体
2楼-- · 2020-02-02 08:23

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;
}
查看更多
疯言疯语
3楼-- · 2020-02-02 08:23

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.
查看更多
看我几分像从前
4楼-- · 2020-02-02 08:27

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.

查看更多
戒情不戒烟
5楼-- · 2020-02-02 08:27

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

查看更多
Melony?
6楼-- · 2020-02-02 08:29

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
        }
    }
}
查看更多
小情绪 Triste *
7楼-- · 2020-02-02 08:29

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

查看更多
登录 后发表回答