Templating - Puzzlout/DevGuidelines GitHub Wiki
Symfony uses Twig template engine. One of the most important Twig features is template inheritance, which let's us to extend base templates and decorate them, just like with OOP.
Let's check default templates. Go in /app/Resource/views/default/base.html.twig, those views are default views for the whole App.
When we create view for our, let's say, ProductController:IndexAction, we put it in /src/AppBundle/Resource/views/, best thing to do would be to store templates in separate subdirectories under views directory, so for example all stuff product related should be /src/AppBundle/Resource/views/product/index.html.twig.
Let's take a quick look at the controller action, go in 'ProductController' or generate one if you wish and create 'IndexAction()' method, instead of returning text Response. We can return Response which contains rendered template.
return $this->render("AppBundle:product:index.html.twig",array('var'=>$var));
So first argument is template path, second is array with key=>value pairs of stuff we want to forward down to template. In this example we can play with 'var' in our index.html.twig tempalte, which will contain the value of $var. We can pass different stuff, like objects, strings, integers whatever we want.
That file should extend base file like this:
{% extends '::base.html.twig' %}
, note that twig file paths should be stated as Bundle:Directory:Filename, we can just put 'base.html.twig' instead of '::base.html.twig' since that's a default template, but we should use their naming conventions whenever we can. Also when we are extending templates, make sure that we put {% extends %}
tag as first line in our twig file.
When we extend the template, everything there should be shown on page when we render it.
Now if you notice 'base.html.twig' contains some stuff called blocks, look at them as class methods that we could override.
If we type in our 'product/index.html.twig' this line {% block title %} Products {% endblock %}
we will override default block called title in our base class, so instead of having 'Welcome!' as our title, our page will have title 'Products'.
Just like with OOP, sometimes when we override method from the class we are extending we still want to run the code from the super class.
Let's take for example inclusion of js files. Base class has the block 'javascripts'. We can then include in our 'base.html.twig' some js files, like jquery or whatever we want, since we want it on every page. But we want to include more js files on specific page and we don't want to include it on all pages, for example gmaps.js. We just type {% block javascripts %} {{ parent() }} <script src='gmaps.js'></script> {% endblock %}
*Note that we should use asset helper for linking assets, take a look on the end of the message to read about it
Look at the {{ var }}
as echo in php, and parent()
will call parent block with the same name.
We can also include files which is something we need if we are talking partial views.
If we want to include template file and pass some variables to it, we can use include tag.
Open up 'product/index.html.twig'. In some block we can include any template we want.
{% block body %} {{ include('AppBundle:includes:sidebar.html.twig',{'var':'passedvalue'}) }} Rest of the body {% endblock %}
Now we create 'includes' directory (directory is optional as it's name) and inside we create sidebar.html.twig.
In it just type something like This is included sidebar {{ var }}
, echoing var should display 'passedvalue' string. If we change the include tag to {{ include('AppBundle:includes:sidebar.html.twig',{'var':var}) }}
, we will pass var all the way down from controller to the included template.
Now of course this is great for some static template inclusion or inclusion of files that depend on the variables that are passed through called template (in our case index.html.twig). For example instead of var we wanted to pass 'categories' and we don't want controller action which is rendering 'product/index.html.twig' to fetch all categories for us and pass it down. We want to decouple that stuff from our index action.
Let's include in our ProductController one action that will fetch us some categories, let's call it "sidebarCategoriesAction" (remember that callable methods should end with "Action" keyword).
public function sidebarCategoriesAction(){
//fetch $categories logic
return $this->render("AppBundle:includes:sidebarcategories.html.twig",['categories'=>$categories]);
}
OK, so we need to create sidebarcategories.html.twig view in AppBundle/Resources/views/includes
Open it up and type something like {% for category in categories %} Category: {{ category.name }}{% endfor %}
(more about for tag http://twig.sensiolabs.org/doc/tags/for.html)
Go in our 'product/index.html.twig', instead of {{ include(...) }}
type this {{ render(controller('AppBundle:Product:sidebarCategories')) }}
This will call that action, render the template sidebarcategories.html.twig and include it. Now we have totally decoupled, if you want to call it, partial module.
If we want to pass down just some things that will modify the output of sidebar categories (let's say some filter), we can modify our sidebarCategoriesAction in our controller to have some params, let's say public function sidebarCategoriesAction($filter)
and change our render call in product/index.html.twig to {{ render(controller('AppBundle:Product:sidebarCategories', {'filter':'somevalue'})) }}
, that will call sidebarCategoriesAction('somevalue').
Few more things that are important, are pipeline filters for {{}}
tag, for example we can quickly display passed down variable in lowercase by typing {{ varname | lower }}
instead of doing that in controller since that's for display only. Twig autoescapes html stuff, so we don't need to escape script tags or something like that, but if we want to display it like that, we can use |raw
filter.
There are more useful filters, you can check them here http://twig.sensiolabs.org/doc/filters/index.html.
For creating hyperlinks between pages, instead of using full paths, we can use path helper so we don't change it every time we decide to change the url of some controller.
<a href="{{ path('routekey') }}>click here! </a>
. routekey should be the name stated in @Route annotation for the controller action, @Route("/path/to/action",name="routekey")
.
Linking assets should be done like this <img src="{{ asset('imagesdir/pic.png') }}" />
Templates are compiled in classes and cached like that, so don't worry about it. In dev mode they are recompiled when they are changed but in production cache should be cleared so they can be recompiled and cached again. So everything will run smoothly. For more info on tags http://twig.sensiolabs.org/doc/tags/index.html
- http://twig.sensiolabs.org/doc/tags/index.html - Twig tags
- http://twig.sensiolabs.org/doc/filters/index.html - Twig filters
- http://symfony.com/doc/current/book/templating.html - Creating and using templates in Symfony