Last active
July 30, 2020 08:16
-
-
Save cwilby/2847624d6533585311e11cdb463e06f5 to your computer and use it in GitHub Desktop.
A PHP Class for inferring variables from a Twig template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks a lot for posting this!
It solves a problem I have perfectly, I just needed to add:
in the
IfNode::class
case, as it would throw a fatal error if there was noelse
node in the template.