It might happen that you are working
on a legacy code that is years old,
with its own templating mechanism1
that doesn’t really allow you to take
advantage of the benefits that a structured
and object-oriented engine like
Twig.
In this situations, when a complete replacement
would cost too much to your organization,
you can take advantage of a wild integration between
this advanced template engine and your
existing code.
This article is outdated! A better approach was described here: http://integrating-twig-in-your-legacy-code-part-2-a-less-wild-approach/
Approach
The main idea is that you should anyway
have a man function which outputs
what is being rendered on the view, so that
you can capture that output and parse it via
Twig, something like a render function
in your controllers:
Example controller
12345678910111213
<?phpclassMy_ControllerextendsFramework_Base_Controller{publicfunctionindexAction(){// ....// do stuff// ...return$this->render('my_template',$parametersForTheView);}}
Example of a base controller
12345678910111213141516
<?phpclassFramework_Base_Controller{publicfunctionrender($templateName,array$parameters=array()){// ....// do stuff to render your template// ...return$templateOutput;// will becomereturn$this->twig->render($templateOutput);}}
At this point the only thing that
you need is to inject the Twig engine
into your base controller and parse the
output of your legacy templates with Twig2:
12345678910
<?php// your actual rendering:return$this->twig->render($templateOutput);// which means:return$this->twig->render('<html><head><title>Hello world</title>...</html>');// so that you can actually write twig in your templates:return$this->twig->render('<html><head><title>{ % block title % }Hello world{ % endblock % }</title>...</html>');
The ‘block’ tag in the example above is having a space between curly brackets and the percentage char since my blog engine (octopress) doesn’t allow those tags them in code blocks. In all of the next examples you will see Twig tags written like that.
Rendering content via Twig
To integrate Twig in your application
it it really a matter of a few minutes:
first, you will have to download and move
the library inside your codebase, then,
thanks to the PSR-0 autoloading (here we
will be using Symfony2’s autoloader, but
you can use any PSR-0 compliant autoloader) you just
need to include it and setup Twig’s own
autoloader:
At this point, let’s get back to our render
function, which we will need to modify in order
to include Twig:
123456789101112131415161718
<?phpclassFramework_Base_Controller{publicfunctionrender($templateName,array$parameters=array()){// ....// do stuff to render your template// we have the HTML output in the $templateOutput variable// ...$twig=newTwig_Environment(newTwig_Loader_String(),array('autoescape'=>false,));return$twig->render($templateOutput,$parameters);}}
At this point we would be already able to write
Twig code inside our templates:
my_index.html
12345678
{ % set posts = registry.get('blog_post').findByUser($user.id) % }<p> { % for post in posts % } ... { % else % } This user didn't write any post { % endfor % }</p>
A new tag
Unfortunately, to support some kind of
inheritance, which is one of the greatest
features of Twig, the situation becomes a little
bit trickier: first of all, we will need to add
to the parsed HTML some extra content to override
blocks, then we will need to create a new Twig
token parser in order to allow declaring multiple
blocks with the same name, which is not allowed by the
block tag.
Let’s say that all of your templates are including
a base layout made of a very clean HTML structure:
After we do it, how can we override these blocks
differently from each controllers’ actions?
You simply include other Twig content at the end of the
generated HTML:
Adding support for basic inheritance
1234567891011121314151617181920212223
<?phpclassFramework_Base_Controller{publicfunctionrender($templateName,array$parameters=array()){// ....// do stuff to render your template// we have the HTML output in the $templateOutput variable// ...$twig=newTwig_Environment(newTwig_Loader_String(),array('autoescape'=>false,));$twigTemplate=sprintf("path/to/twig/templates/%s.twig",$templateName;if(file_exists($twigTemplate))){$templateOutput.=file_get_contents($twigTemplate);}return$twig->render($templateOutput,$parameters);}}
And then override the content with your own twig template:
path/to/twig/templates/templateName.twig
123
{ % block title % } About: this is our about page
{ % endblock % }
After you setup everything, you will realize that
there is a huge problem here: since Twig doesn’t allow
to declare blocks Twig, you can use the block tag!
To overcome the problem, you can simply add a new tag,
partial:
<?php/** * Token parser for the twig engine that adds support to redefinable blocks, * under the 'partial' alias. * * IE: * { % partial myPartial % }First{ % endpartial %} * { % partial myPartial % }Second{ % endpartial %} * * will render "Second". * * This is needed since the { % block % } tag doesnt support redefining blocks * with the string loader, it just supports it via inheritance. */classPartialTokenParserextendsTwig_TokenParser_Block{/** * Parses the twig token in order to replace the 'partial' block. * * @param Twig_Token $token * @return Twig_Node_BlockReference * @throws Twig_Error_Syntax */publicfunctionparse(Twig_Token$token){$lineno=$token->getLine();$stream=$this->parser->getStream();$name=$stream->expect(Twig_Token::NAME_TYPE)->getValue();$this->parser->setBlock($name,$block=newTwig_Node_Block($name,newTwig_Node(array()),$lineno));$this->parser->pushLocalScope();$this->parser->pushBlockStack($name);if($stream->test(Twig_Token::BLOCK_END_TYPE)){$stream->next();$body=$this->parser->subparse(array($this,'decideBlockEnd'),true);if($stream->test(Twig_Token::NAME_TYPE)){$value=$stream->next()->getValue();if($value!=$name){thrownewTwig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)",$value),$stream->getCurrent()->getLine(),$stream->getFilename());}}}else{$body=newTwig_Node(array(newTwig_Node_Print($this->parser->getExpressionParser()->parseExpression(),$lineno),));}$stream->expect(Twig_Token::BLOCK_END_TYPE);$block->setNode('body',$body);$this->parser->popBlockStack();$this->parser->popLocalScope();returnnewTwig_Node_BlockReference($name,$lineno,$this->getTag());}/** * Returns the tag this parses will look for. * * @return string */publicfunctiongetTag(){return'partial';}/** * Decides when to stop parsing for an open 'partial' tag. * * @param Twig_Token $token * @return bool */publicfunctiondecideBlockEnd(Twig_Token$token){return$token->test('endpartial');}}
Then you just need to tell the Twig environment to
add this token parser:
{ % partial title % } About: this is our about page
{ % endpartial % }
So?
If you spot any typo /
mistake please do let me know: I wrote the
example code adapting the one I had from a previous
project so it might be that something slipped my
mind.
Since I never dug that deep into
Twig it might be that some things
could be done in a cleaner way, so if
you have suggestions or feedbacks I would
strongly encourage you to go
berserk mode in the comments section below.