January 18, 2012

Customizing Templates of symfony2 Vendor Bundles

Today I wanted to add tinymce to my form fields in the backend. I use SonataAdminBundle (SAB) to create my admin interface.

tinymce and symfony2?
Nothing easier than that, thanks to the active community there is a bundle for that: http://knpbundles.com/stfalcon/TinymceBundle (TMB)

Now, how to tell SAB to use tinymce?

1st approach: customize vendor template
As described on the TMB page:
1. Add configuration settings
2. Add {{tinymce_init()}} at the bottom of TABs standard_layout.html.twig (stored at vendor\bundles\Sonata\AdminBundle\Resources\views)
3. Add tinymce css attributes to your form field:

thats it, I now had tinymce in my admin form.
But this solution lacks of one, for me very important, advantage: my template customizations will be overriden by SAB updates.

2nd approach: overriding / extending vendor template
I followed the instructions in the symfony2 book at: http://symfony.com/doc/current/book/templating.html#overriding-bundle-templates
But I soon realized that this meachanism does not provide what I want. Template overrides do just what they say they do, they overwrite. Since I only wanted to overwrite certain blocks of the SAB layout template, and not redefining the whole, I was stuck here. The contents of my custom template at app\Resources\SonataAdminBundle\views\standard_layout.html.twig was:

The extends directive on top lead to an endless loop. Without the extends directive, just the contents of my custom template where rendered (without the contents from the original template).

3rd approach: bundle inheritance
Okey, next try. I used http://sonata-project.org/bundles/easy-extends/master to create an extension bundle. The code of the new bundle (called ApplicationSonataAdminBundle) was generated in app/Application/. I copied it over to src/. Afteer that I registered the bundle in AppKernel. Then I copied my standard_layout.html.twig to the Resources\views directory of the new bundle.
Finally, after calling assets:install and cache:clear --no-warmup the new bundle has been published and invoked by symfony2. But the template did still not work. Allthough the two bundles (original and derived) have different names, symfony2 was still stuck in an endless loop. Hmmm, what now?

4th approach: service container
A question on stackoverflow lead me on the right way (http://stackoverflow.com/questions/8729439/sonataadminbundle-custom-rendering-of-text-fields-in-list).
When diving into bundles\Sonata\AdminBundle\DependencyInjection\Configuration.php I noticed that there must be a way to overwrite the default layout template. So I openend my app/config/config_dev.yml and added the following:

that worked. But you might ask me now, where the hack does my_standard_layout.html.twig come from? You have two options:
- In the app\Resources\SonataAdminBundle\views folder
- In the extension bundle at src\ApplicationSonata\AdminBundle\Resources\views
Anyhow, you can now use the extends directive without causing an endless loop. And that enables you to customize just the blocks you want. The blocks are defined in the original bundle template that you are extending.

You can even add {{ parent() }} before you initialize tinymce to preserve the original content.