GeSHi, Symfony2, and Twig Extensions

Craig Blanchette on Google+ on May 10th 2013

I've recently changed all of the syntax highlighting on the site to use GeSHi instead of SyntaxHighlighter. A couple reasons I wanted to do this is to make my syntax highlighting match that of the Symfony2 docs so that people who find the tutorials will find it easier to follow, and also because GeSHi is done server side in PHP and not client side in Javascript as SyntaxHighlighter does. This is not a big performance hit on the server side as I use a reverse proxy so I really only have to generate the code once every time it is changed. 

The first thing we'll need to do is acquire GeSHi - It's not currently available officially for composer but I found this repository on GitHub for EasyBook. If you've never heard of EasyBook I would suggest taking a look. It uses GeSHi and Twig among other things to help you compile eBooks and PDF's and even uses Symfony's EventDispatcher to help you do some pretty cool things. This package also includes highlighting for Twig as well, which I definitely needed. The package name is called "easybook/geshi" so let's add that to our composer.json:

  1. {
  2. ...
  3. require: {
  4. "easybook/geshi": "dev-master"
  5. }
  6. ...
  7. }

And then let's download it:

php composer.phar update easybook/geshi

Since I'm moving from SyntaxHighlighter, and I don't really feel like going through all of my posts, I decided to parse the output of my blog post content and search for these instances. A typical pre block looks something like this:

  1. <pre class="brush: xml">
  2. <h1>I am HTML</h1>
  3. </pre>

This is pretty easy to grab with a regular expression. Now, wouldn't it be nice if we could do something like this in our Twig templates?

  1. <article>
  2. {{ Post.content|geshi_pre }}
  3. </article>

Yes, it would - so let's do it. The first thing we'll need to do is create a Twig Extension so that we can add our own filter:

  1. namespace Isometriks\Bundle\WebsiteBundle\Twig\Extension;
  2.  
  3. class GeshiExtension extends \Twig_Extension
  4. {
  5. public function getFilters()
  6. {
  7. return array(
  8. 'geshi_pre' => new \Twig_Filter_Method($this, 'replaceGeshiPre'),
  9. 'geshi' => new \Twig_Filter_Method($this, 'geshiHighlight'),
  10. );
  11. }
  12.  
  13. public function geshiHighlight($source, $language)
  14. {
  15. $geshi = new \GeSHi($source, $language);
  16. $geshi->enable_classes();
  17.  
  18. return $geshi->parse_code();
  19. }
  20.  
  21. public function replaceGeshiPre($string)
  22. {
  23. $pattern = '#<pre class="brush: (.+?)">(.+?)</pre>#si';
  24.  
  25. $output = preg_replace_callback($pattern, function($matches) {
  26. return $this->geshiHighlight($matches[2], $mathes[1]);
  27. }, $string);
  28.  
  29. return $output;
  30. }
  31.  
  32. public function getName()
  33. {
  34. return 'geshi_highlight';
  35. }
  36. }

So now we have two Twig filters we can use, the geshi_pre filter will filter through my pre tags that were previously used for SyntaxHighligher, and the geshi filter can be used like this:

  1. {{ '<h1>This is HTML</h1>'|geshi('xml') }}

It's used directly on source code. This may be useful in other situations, but for my blog I'm not writing Twig templates, I'm writing a full page of content that goes into a Twig template so we need to parse the content ourselves.

Now that we've created the Twig Extension, we have to add it to the container with a tag:

  1. <services>
  2. <service id="isometriks_website.twig.extension.geshi_highlight" class="Isometriks\Bundle\WebsiteBundle\Twig\Extension\GeshiExtension">
  3. <tag name="twig.extension" />
  4. </service>
  5. </services>

After that you should be all set. Since we've kept our code DRY, you can just edit the geshiHighlight method to turn on line numbers and other features, check out GeSHi's documentation for more information.