symfony/symfony — pull request #6521 by rafalwrzeszcz (2012-12-30 14:42:18+00:00)
Title: [FrameworkBundle] Proxy templating engine.

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: -
Todo: -
License of the code: MIT
Documentation PR: -

# Description

New "proxy" templating engine allows to delegate template rendering to underlying engine without changing template name. It is a bit like `DelegatingEngine` but in `DelegatingEngine` target engine is resolved based on template name, while here template name is constant and resolving is done based on configuration (most likely DI).

This engine simply translates view name for example `template.html.default` into `template.html.php` (implementation allows to translate from any suffix to any suffix).

# Configuration

In order to use it you first need to add *default* engine to enabled engines in framework bundle configuration:

```yaml
framework:
templating:
engines: ["php", "twig", "default"]
```

You can then switch default templating engine (defaults to *twig*):

```yaml
framework:
templating:
default_engine: "php"
```

**Note:** *default* engine is registered with `services.xml` file as most useful instance, but you can define different proxies if you want.

# The sole problem

Why at all I created this? I faced a problem with template engine switching in my bundle. Some common vendor bundles (like *FOSUserBundle*) try to provide such feature. I know most **Symfony 2** projects use **Twig**, but [I personaly dislike it](http://wrzasq.pl/blog/chilldevviewhelpersbundle-php-templating-helpers-for-symfony-2.html) and still opt for using plain **PHP** templates. I don't want to start any flame war here, yet since Symfony allows templating engine diversity it's a good idea to provide templating switch in generic bundles, so they can be used in projects regardles of templating engine they use - even **Smarty** for example.

The main problem with `DelegatingEngine` is, that even though it contains multiple engines, you still have to declare which one you want to use at call-time - if you use *Twig* you need to render `file.html.twig`, if *PHP* then `file.html.php`. So bundle creators need to implement template switch option for each bundle, then inject it into controllers where view references are created and choose view name basing on it. What's worse - such approach makes `@Template` annotation useless since it's *engine* attribute is static and is not evaluated.

## ProxyEngine comes in

`ProxyEngine` allows bundles creators to keep templates references static, while still having possibility to switch between engines. Using pre-configured *default* proxy one can use `Bundle:Controller:action.html.default` as constant template name and it will be resolved at rendering time and forwarded to desired engine choosed from configuration.

This way it's now possible to use `@Template` annotation with controller actions and having template switch in your bundle out-of-the-box:

```php
class Controller
{
/**
* @Template(engine="default")
*/
public function actionAction()
{
return ['foo' => 'bar'];
}
}
```

**Note:** you don't create any physical files like `file.html.default` - template name resolving is done before real rendering is done and your templates are still named `file.html.twig`/`file.html.php`.

# Streaming

Like with `DelegatingEngine`, even though `ProxyEngine` implements `StreamingEngineInterface` it doesn't mean you can use streaming anytime you want - it just allows to proxy streaming of real engine. If it is not supported by destination engine, `ProxyEngine` will throw exception.

# Performance

Ok, to state it clear - I did not perform any performance tests. Just basing on own thoughts I don't think using this proxy can generate much impact. Of course there is a problem with re-calling `templating` service (which is usually `DelegatingEngine`), but `ProxyEngine` calls it with already parsed template reference so there is no need for parsing any new template references.

# Pitfails

You need to avoid…

## …referencing to same engine

```yaml
framework:
templating:
default_engine: "default"
```

this would lead to infinite translating view name to itself.

## …having single engine configured

```yaml
framework:
templating:
engines: ["default"]
```

this will not lead you anywhere since *default* is just a proxy and need destination engine.