I'm modernizing a legacy app using Symfony2 components. I've been trying (and mostly failing) to replace the old php templates with twig ones.
The part I'm struggling with is : each subtemplate has its own class containing its own logic (Told you it's all about legacy).
So, I created a twig extension that calls the template class and then includes the sub template passing it the class defined variables (Here's the extension code).
e.g:
{% template "NavBlockTemplate" %}
NavBlockTemplate
instance.
getTemplateName
to get the twig template file to includegetVariables
to get the vars needed by the templateTwig_Node_Include
of said template with given varsThe sad part here is : each template can pass variables to it's subtemplate class constructor ...
So, what I'd need, but not sure it is even possible is something like :
{% template "NavBlockTemplate" with { 'varName': value, 'var_id': otherVar.id }
Twig_Expression
objects to php varsNavBlockTemplate
instance with php compiles vars
getTemplateName
to get the twig template file to includegetVariables
to get the vars needed by the templateTwig_Node_Include
of said template with given varsSo, Is that possible ? Any tips on how to achieve that ?
Variable values cannot be accessed during the compilation of the template. They are not available yet.
Twig has 2 distinct phases when you call render($name, $context)
:
The 2 steps are easily seen by the implementation of Twig_Environment::render()
:
public function render($name, array $context = array())
{
return $this->loadTemplate($name)->render($context);
}
Your custom tag needs to account for that. It will need to create a special node class which will get compiled into the logic you need. you can look at the way existing Twig tags are implemented.
Even the class name you include can be accessed at compile time like you did. $expr->getAttribute('value')
will work only if the expression is a constant expression, and you don't enforce it in your parser.
On the other hand, using a tag in this case is probably not the best solution (while it is the most complex one). According to the semantic of Twig, a function would be better. This is precisely why Twig also introduced a include()
function as it fits better. this is how it would look like.
in the template:
{{ include_legacy("NavBlockTemplate", { 'varName': value, 'var_id': otherVar.id }) }}
in the extension:
class LegacyIncludeExtension extends \TwigExtension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction(
'include_legacy',
array($this, 'includeLegacy'),
array('is_safe' => array('all'), 'needs_environment' => true, 'needs_context' => true)
),
);
}
public function includeLegacy(\Twig_Environment $env, array $context, $name, array $variables = array())
{
$fqcn = // determine the class name
$instance = new fqcn();
$template = $instance->getTemplateName();
$variables = array_merge($instance->getVariables(), $variables);
return $env->resolveTemplate($template)->render(array_merge($context, $variables));
}
}
The last line of the method performs the main work of twig_include
. If you need support for isolating the context, it is quite easy (make the array merge of the template conditional). Support for ignore_missing is more work, and you would be better at calling twig_include
directly in such case:
public function includeLegacy(\Twig_Environment $env, array $context, $name, array $variables = array(), $withContext = true, $ignoreMissing = false)
{
$fqcn = // determine the class name
$instance = new fqcn();
$template = $instance->getTemplateName();
$variables = array_merge($instance->getVariables(), $variables)
return twig_include($env, $context, $template, $variables, $withContext, $ignoreMissing);
}