How can I override **partially** a third-party template? - symfony

I need to override a template from third-party bundle by following one of the Symfony's built-in conventions. The Symfony documentation talks about them:
To override the bundle template, just copy index.html.twig template from the bundle to app/Resources/AcmeBlogBundle/views/Blog/index.html.twig (the app/Resources/AcmeBlogBundle directory won't exist, so you'll need to create it). You're now free to customize the template.
You can also override templates from within a bundle by using bundle inheritance. For more information, see How to Use Bundle Inheritance to Override Parts of a Bundle.
While this approach may work, it can be overly complicated if you only need to override a small portion of the template (e.g. some blocks). Additionally, if the third-party bundle updates its own template, your version of the template may become outdated and require updates to stay current with the latest changes.
This is what I've tried to do without success:
{# app/Resources/AcmeBlogBundle/views/Blog/layout.html.twig #}
{% extends '#AcmeBlog/Blog/layout.html.twig' %}
{% block title %}My Default Title{% endblock %}
The above code doesn't work. It breaks after reaching the maximum execution time when I've accessed this page and the clear cache command never ends.
Why it doesn't works and how to achieve it without copying the entire parent template from the third-party bundle?
Related issues and pull-requests without workaround:
https://github.com/symfony/symfony/issues/1966 (2011)
https://github.com/symfony/symfony/pull/2202 (2011)
https://github.com/symfony/symfony-docs/issues/752 (2011) | Unrelated.
https://github.com/twigphp/Twig/issues/1334 (2014)
https://github.com/symfony/symfony/issues/15755 (2015)
https://github.com/symfony/symfony/issues/17054 (2015)
https://github.com/symfony/symfony/issues/17407 (2016)
https://github.com/symfony/symfony/issues/17557 (2016)

Why it doesn't works?
{# app/Resources/AcmeBlogBundle/views/Blog/layout.html.twig #}
{% extends '#AcmeBlog/Blog/layout.html.twig' %}
{% block title %}My Default Title{% endblock %}
The reason comes from Twig's paths and namespaces auto-configuration between bundles, their children and Symfony's path convention. By default Symfony/Bundle/TwigBundle assign the same Twig's namespace (AcmeBlog) for all paths, following this order:
# config.yml
twig:
paths:
# Auto-configuration behind the scenes for TwigBundle extension:
# (1st) if AcmeBlogChildBundle has as parent to AcmeBlogBundle
'src/AcmeBlogChildBundle/Resources/views': AcmeBlog
# (2nd) Path to override bundles (Symfony's convention)
'app/Resources/AcmeBlogBundle/views': AcmeBlog
# (3rd) third-party bundle
'vendor/acme/blog-bundle/Resources/views': AcmeBlog
That means, if you need to render the #AcmeBlog/Blog/layout.html.twig template, Twig try to find it in all paths (in the order has been defined) whose namespace matches AcmeBlog.
So to override it, you had to create this template (/Blog/layout.html.twig) in (1st) or (2nd) paths. But, if you're extending from #AcmeBlog/Blog/layout.html.twig at the same time (thinking about extends from the original template) then Twig performs the same previous procedure, causing a circular template reference (infinite loop) and never reach the (3rd) path.
How to achieve it without copy the entire parent template from the third-party bundle?
Symfony's built-in workaround
Let's define a different Twig's namespace for this third-party bundle in twig paths configuration:
# config.yml
twig:
paths:
# (4th) third-party bundle alias.
# The path can be relative to project or real path
vendor/acme/blog-bundle/Resources/views: AcmeBlogOriginal
Later, use the namespace alias to avoid circular reference when you're overriding a third-party template that extends from the original:
{# app/Resources/AcmeBlogBundle/views/Blog/layout.html.twig #}
{% extends '#AcmeBlogOriginal/Blog/layout.html.twig' %}
{% block title %}My Default Title{% endblock %}
Thus, you're able to override blocks instead the whole template. Even should work out-of-the-box as it's about Twig's paths only.
Since Symfony 3.4 this issue has been solved as built-in feature.
http://symfony.com/blog/new-in-symfony-3-4-improved-the-overriding-of-templates#overriding-and-extending-templates

Related

Symfony bundle inheritance and twig templates overriding

I have a third party bundle called VendorDeliveryBundle.
I want to override one of its twig templates that i called with this syntax in one of my App twig templates:
{% include '#VendorDelivery/Frontend/Booking/_delivery.html.twig' with { 'form': form } only %}
Like this it works, the vendor template is called.
But if i want to override this template by registering the overriding bundle in AppKernel (as described in https://symfony.com/doc/2.8/bundles/inheritance.html) and by creating:
App/DeliveryBundle/Resources/views/Frontend/Booking/_delivery.html.twig
this template doesn't override the vendor template.
But if i use this syntax instead
{% include 'VendorDeliveryBundle:Frontend:Booking/_delivery.html.twig' with { 'form': form } only %}
the template is overridden.
It seems that the # syntax doesn't work as expected.
So i'm wondering if it is a bug or a normal behavior considering this symfony documentation https://symfony.com/doc/2.8/bundles/inheritance.html:
The overriding of resources only works when you refer to resources with the #FOSUserBundle/Resources/config/routing/security.xml method. If you refer to resources without using the #BundleName shortcut, they can't be overridden in this way.
Thanks,
# in twig syntax is a feature called "namespaced paths". (documentation)
Use functionality of {% include 'Bundle:Folder:template' %} it is not the same thing as {% include '#Bundle\Folder\template' %}.
For sample, if you overriding fosub bundle:
{% include '#FOSUser/Security/login.html.twig' %} {# Will be fosub template #}
{% include '#User/Security/login.html.twig' %} {# Will be overrided template #}
{% include 'FOSUserBundle:Security:login.html.twig' %} {# Also will be overrided template #}
Also, i want to add, that if you want to override only a template (without global functionality as controllers, listeners, etc) you can added templates to you app directory. It is well described in this documentation

Override Controller, Views and Resources in your own Symfony Bundles from other Bundles

How can you override your own vendor/bundle controller or view from another bundle?
Symfony has a default way of overriding third party vendor bundles by adding files in your own app or src directory, e.g. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig or src/Acme/BlogBundle/Resources/views/Blog/index.html.twig.
Override Any Part of a Symfony Bundle
How to Use Bundle Inheritance to Override Parts of a Bundle
Overriding Bundle Templates
But I have my own custom core vendor bundles in the src directory (e.g. src/Gutensite/CmsBundle) and I need to override controllers, views (twig) and resources (images, css) in template specific bundles that should take precedence whenever they need to alter the default behavior of the platform for a specific design (e.g. src/Templates/LunarBundle/).
Views and Resources
So to overwrite the CmsBundle view in my Lunar template, I may put files here:
Templates/LunarBundle/Resources/GutensiteCmsBundle/views/dash.html.twig
Templates/LunarBundle/Resources/GutensiteCmsBundle/public/css/dash.css
Templates/LunarBundle/Resources/GutensiteCmsBundle/public/images/icon.png
If I make my own dash.html.twig template I could reference the files locally, e.g.
{% stylesheets '#TemplatesAdminBundle/Resources/GutensiteCmsBundle/public/css/dash.css' %}
<link rel="stylesheet" href="{{ asset_url }}">
{% endstylesheets %}
But if I only added a custom image or css, the original Gutensite\CmsBundle\Resources\views\dash.html.twig template would find the custom css in my TemplatesLunarBundle when it was referenced as:
{% stylesheets '#GutensiteCmsBundle/Resources/public/css/dash.css' %}
<link rel="stylesheet" href="{{ asset_url }}">
{% endstylesheets %}
But Symfony default behavior doesn't allow you to override your own bundles from your own bundles. So how do you do this?
Controllers
I need the same overriding capabilities for controllers. One template bundle (e.g. Templates\LunarBundle may need to overwrite a lot of different core vendor bundles (e.g. Gutensite\CmsBundle\Controllers\DashController.php and Gutensite\ArticleBundle\Controllers\ArticleController.php). So this method to reference the "parent" wouldn't work, since that's designed for OneToOne bundle overriding.
Currently the only method I know of is to tell Symfony to find alternative files in the same namespace, e.g. in my primary controller I register the namespace Gutensite to alternative locations.
$loader = $GLOBALS['loader'];
// path to the user's current template
$loader->add('Gutensite', $template->getPath().'/src', true);
// path to the user's custom client files
$loader->add('Gutensite', \Gutensite\PATH_CLIENT.'/src', true);
And then I would put my custom controllers in a directory like this in my template:
Templates\LunarBundle\src\Gutensite\CmsBundle\Controllers\DashController.php
This works, but I'd like feedback on potential problems or better solutions.
Maybe this is something for you: https://github.com/liip/LiipThemeBundle
This bundle provides you the possibility to add themes to each bundle. In your bundle directory it will look under Resources/themes/ or fall back to the normal Resources/views if no matching file was found.
Especially see the configuration for Theme Cascading Order

How to extend twig template from parent bundle?

I'm learning symfony2 and like it very much (migrating from ZendFramework 1.x) but I'm stuck now with twig templating. Can you point me to the right direction?
My application has many customers and each has some customization (mostly templates, but also controllers, etc.).
I try to achieve this by bundle inheritance / I created ClientBundle which inherits from BaseBundle holding all basic code and templates... it works as I need for everything bud templates. When I create template in ClientBundle it overlays the template from BaseBundle and I cannot get access from the ClientBundle template to the original template from BaseBundle. I would like to extend the parent template, not overlay it... is this impossible?
Thank you!
Using the standard Symfony extends notation to reference a parent's bundle template results in fatal error. Symfony interprets your template reference to BaseBundle as ChildBundle as a result of bundle inheritance causing a function nesting level exception.
You can reference the parent's bundle template file directly using the following notation:
{% extends "#Client/Foo/bar.html.twig" %}
in replacement for:
{% extends "ClientBundle:Foo:bar.html.twig" %}
Twig's FilesystemLoader splits the reference string into namespace (Client) and path (Foo/bar.html.twig). Namespace is your bundle name without the Bundle suffix. Full path for each namespace is resolved by concatenating default <bundle_path>/Resources/views/ with supplied path.
The standard command in twig template for extention is:
{% extends 'BaseBundle::yourTemplate.html.twig' %}
You probably use the same name for template in your child bundle (you probably have a yourTemplate.html.twig in both your parent and your child bundle so the child template overrides the parent). In this case, you can rename your child template (yourTemplate2.html.twig) and then extend the child template from parent. More about bundles inheritance and twig templates extentions:
http://symfony.com/doc/current/cookbook/bundles/inheritance.html
http://symfony.com/doc/current/book/templating.html

FOSUserBundle - overriding templates

I've just installed FOSUserBundle and I'm trying to override the templates to use my own custom layout.
I have a bundle, BMCmsBundle, that has a layout.html.twig file in
src/BM/CmsBundle/Resources/views/layout.html.twig
Looking through the docs, I have created the following file:
app/Resources/FOSUserBundle/views/layout.html.twig
How do I then use my own template, i.e. CSS/jS?
I've tried in the FOS layout.html.twig file to use the following:
{% extends 'BMCmsBundle::layout.html.twig' %}
But once again, this doesn't seem to work, any ideas why?
Thanks
After creating your new layout at
app/Resources/FOSUserBundle/views/layout.html.twig
Make sure you also clear your cache:
php app/console cache:clear
It is the opposite.
Your layout in BMCmsBundle has to extend the one in app/Resources with the command :
{% extends "::layout.html.twig" %}

How to reference twig for custom field types in dedicated bundle?

I am (still) trying to introduce http://xoxco.com/clickable/jquery-tags-input into a dedicated bundle. As far, I have a type as a child of text and a data transformer that converts comma-separated strings into arrays of Objects and vice versa.
Now I want to decorate the text field with the JQuery code linked above. As far as I understand, I have to define a block like
{% block manytomanycomboselector_widget %}
{% spaceless %}
{{ block('text_widget') }}
<script>
$(function(){
$("#{{ id }}").tagsInput();
});
</script>
{% endspaceless %}
{% endblock manytomanycomboselector_widget %}
in [MyTypeBundle]Resources/views/Form/fields.html.twig
Now, both the documentation and the answers for this question at StackOverflow state that I have to reference fields.html.twig somewhere either in the template that uses the form or in app/, but this doesn't seem to be necessarily for other field-type bundles, though I cannot see in their code why.
What do I have to configure inside the bundle besides this block in this file?
Also I didn't get where I have to put the css and js requirements for the header and how I deal with general requirements like jQuery itself.
I have the same issue & I resolve it by merging my field template in the twig.form.resources parameter.
So, in the DI extension of my bundle (MyBundle/DependencyInjection/MyBundleExtension.php), I add:
$container->setParameter('twig.form.resources', array_merge(
array('MyBundle:Form:field_widget.html.twig'),
$container->getParameter('twig.form.resources')
));
Be aware, your bundle must be registered after the TwigBundle in your AppKernel.
EDIT:
A form field is not linked to any JS or CSS. So, IMO, you have 2 solutions.
Firstly, you directly wrap your JS & CSS in your field template and your bundle stays stand-alone.
Secondly, you instruct final users that they need to include manually some JSS & CSS each time they use your field type.
The IoFormBundle & GenemuFormBundle uses the second solution like explain in their documentation.

Resources