Getting acquainted with Twig

By the end of this lesson, you’ll understand how Twig builds the frontend and have made some minor changes to the standard page. Along the way, we'll be

  • Learning about controllers and learning how information is passed to templates
  • Discussing the Twig templating language
  • Learning about the different Symfony environments
Follow along using the code available from this GIT repository.
Start - master
End - master

The Symfony Controller

Before any page gets rendered, Symfony handles the request with a controller. A controller is the piece of code which is responsible for handling input and returning some form of output. Open the following file:

  1. <?php
  2. // # src/AppBundle/Controller/DefaultController.php
  3. namespace AppBundle\Controller;
  5. // Include Symfony functionality
  6. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
  7. use Symfony\Bundle\FrameworkBundle\Controller\Controller;
  8. use Symfony\Component\HttpFoundation\Request;
  10. class DefaultController extends Controller
  11. {
  12.     /**
  13.      * @Route("/", name="homepage")
  14.      */
  15.     public function indexAction(Request $request)
  16.     {
  17.         // Render the template and pass working directory as parameter
  18.         return $this->render('default/index.html.twig', [
  19.             'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..').DIRECTORY_SEPARATOR,
  20.         ]);
  21.     }
  22. }

Namespace declaration

At the start of the file, you should see is a namespace declaration. There will be a namespace declaration present in every PHP file throughout Symfony for the sake of autoloading according to the PSR4 standard.

Namespaces are a way to isolate and encapsulate code avoiding conflict in the same way you have many houses on a street identified by numbers.
They can also have aliases which allows you to easily drop in replacement classes for certain functionality so, when it comes to customising parts Symfony, the process is made simple without the need to alter any existing code.

Symfony framework

Next up are declarations for namespaces which will be used for creating the controller, pulling in functionality from other locations in Symfony. These are the libraries which come with the Symfony Standard Edition and (possibly aside from the Request class) will be present in all controller definitions.

  • Route allows us to define navigation in the form of annotations.
  • Controller provides the base class for handling the request / reponse logic
  • Request creates a bag of information about the HTTP request and user session

Class and route definition

Extending the Controller class that got imported, your controller should be named having "Controller" as a suffix and method names (if they are requests) having "Action".

In between these two declarations is a comment with a route definition. This is known as an annotation and they are a way to hook extra functionality in to the code without having to write any actual code. In the case of the route annotation, the first parameter is the URI and the second parameter a reference name for the route.

Returning a response from the controller

The return from a Symfony controller must always be in the form of a response object which is made up of header information and some content. In this case, the render function is a shortcut method finds and renders a template for us and then also constructs the response object.

There are other response classes available provided by Symfony which make simple messages, JSON transmission, redirects, etc. much simpler which can be found in the Symfony\Component\HttpFoundation namespace.


The Default controller returns a rendered template which by default uses app/Resources/views for the template path so the controller would try to load app/Resources/views/default/index.html.twig.
Any parameters which you want to pass to the template are simply contained in an associative array as the second parameter and, in this case, a parameter named base_dir which is the path of your Symfony project is being passed.

The default template engine is Twig, also created by SensioLabs like the core of Smyfony itself. Twig is designed to be very close to bare HTML providing only short and simple features for the template designer.
This keep it simple approach was a design decision made on purpose in order to keep the emphasis on presentation but, it is possible (and not very difficult) to enhance with extensions. For now, open up the following two files

  1. {# app/Resources/views/base.html.twig #}
  3. <!DOCTYPE html>
  4. <html>
  5.     <head>
  6.         <meta charset="UTF-8" />
  7.         <title>{% block title %}Welcome!{% endblock %}</title>
  8.         {% block stylesheets %}{% endblock %}
  9.         <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
  10.     </head>
  11.     <body>
  12.         {% block body %}{% endblock %}
  13.         {% block javascripts %}{% endblock %}
  14.     </body>
  15. </html>
  1. {# app/Resources/views/default/index.html.twig #}
  3. {% extends 'base.html.twig' %}
  4. {% block body %}
  5.     <div id="wrapper">
  6.         <div id="container">
  7.             <div id="welcome">
  8.                 <h1><span>Welcome to</span> Symfony {{ constant('Symfony\\Component\\HttpKernel\\Kernel::VERSION') }}</h1>
  9.             </div>
  11.             <div id="status">
  12.                 <p>
  13.                     <svg id="icon-status" width="1792" height="1792" viewBox="0 0 1792 1792" xmlns=""><path d="..." fill="#759E1A"/></svg>
  15.                     Your application is now ready. You can start working on it at:
  16.                     <code>{{ base_dir }}</code>
  17.                 </p>
  18.             </div>
  20.             <div id="next">
  21.                 <h2>What's next?</h2>
  22.                 <p>
  23.                     <svg id="icon-book" version="1.1" xmlns="" x="0px" y="0px" viewBox="-12.5 9 64 64" enable-background="new -12.5 9 64 64" xml:space="preserve">
  24.                         <path fill="#AAA" d="..."/>
  25.                     </svg>
  27.                     Read the documentation to learn
  28.                     <a href="{{ constant('Symfony\\Component\\HttpKernel\\Kernel::VERSION')[:3] }}/book/page_creation.html">
  29.                         How to create your first page in Symfony
  30.                     </a>
  31.                 </p>
  32.             </div>
  34.         </div>
  35.     </div>
  36. {% endblock %}
  38. {% block stylesheets %}
  39. <style>
  40.     ...
  41. </style>
  42. {% endblock %}

Base template

  • A template can be made up of simple HTML
  • Throughout the HTML, there are block declarations. These define content spaces which child templates can use to fill with content. These can be empty or contain content, it doesn't matter. In the case of having content, if used in a child template, the contents are replaced unless you include the following within the block which would place the parent content at the top of the block.
    1. {% block ... %}
    2.    {{ parent()}}
    3.    Your child block content
    4. {% endblock %}

Index template

I have replaced some of the content with ellipsis to make it more presentable for this tutorial

  • The page starts with an extends declaration. This simply declares that this template inherits structure (and possibly content) from another template.
    1. {% extends ... %}
  • Throughout the body block, there are values enclosed within double curly braces. The curly braces with a percent sign are used to execute statements wheras the double braces are used to print the result of an expression. This can be either a function, such as getting a constant from Symfony or paremeters that are passed to the template, in this case the base_dir variable.
    1. {{ base_dir }}


Change something in either of the templates and reload the page; nothing changed in your browser. This is because Symfony creates a set of caches for various functions and frontend views are no exception. By default, we are using the production environment so, in order to have the changes reflected, we need to clear the cache.

  1. php bin/console cache:clear --env=prod

Reload the page and you should see the changes. This is impractical for development though where you want to test changes quickly. In Symfony, we can run the website using various environments, you may notice in the above that we cleared the production using the env parameter.

To Symfony, an environment is nothing more than a key which gets set early in the pipeline. In reality, this key is used to provide different code paths and the Standard Edition prepares three environments for us right from the get go.

Production The optimised pipeline
Development Caches are regenerated every request, debugging enabled
Testing Used for PHPUnit testing

If you setup your environment using the direct tutorial, you may recall setting the DirectoryIndex to app.php. Open the following files

  1. <?php
  3. use Symfony\Component\HttpFoundation\Request;
  5. /** @var \Composer\Autoload\ClassLoader $loader */
  6. $loader = require __DIR__.'/../app/autoload.php';
  7. include_once __DIR__.'/../var/bootstrap.php.cache';
  9. $kernel = new AppKernel('prod', false);
  10. $kernel->loadClassCache();
  11. //$kernel = new AppCache($kernel);
  13. // When using the HttpCache, you need to call the method in your front controller instead of relying on the configuration parameter
  14. //Request::enableHttpMethodParameterOverride();
  15. $request = Request::createFromGlobals();
  16. $response = $kernel->handle($request);
  17. $response->send();
  18. $kernel->terminate($request, $response);
  1. <?php
  3. use Symfony\Component\HttpFoundation\Request;
  4. use Symfony\Component\Debug\Debug;
  6. // If you don't want to setup permissions the proper way, just uncomment the following PHP line
  7. // read
  8. // for more information
  9. //umask(0000);
  11. // This check prevents access to debug front controllers that are deployed by accident to production servers.
  12. // Feel free to remove this, extend it, or make something more sophisticated.
  13. if (isset($_SERVER['HTTP_CLIENT_IP'])
  14.     || isset($_SERVER['HTTP_X_FORWARDED_FOR'])
  15.     || !(in_array(@$_SERVER['REMOTE_ADDR'], ['', '::1']) || php_sapi_name() === 'cli-server')
  16. ) {
  17.     header('HTTP/1.0 403 Forbidden');
  18.     exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
  19. }
  21. /** @var \Composer\Autoload\ClassLoader $loader */
  22. $loader = require __DIR__.'/../app/autoload.php';
  23. Debug::enable();
  25. $kernel = new AppKernel('dev', true);
  26. $kernel->loadClassCache();
  27. $request = Request::createFromGlobals();
  28. $response = $kernel->handle($request);
  29. $response->send();
  30. $kernel->terminate($request, $response);

For now, don't concern yourself with the specifics of each file, just take note that when the kernel is created there is a different first parameter used in each file which sets the environment key.

  1. $kernel = new AppKernel(‘dev’, true);

Go back to your browser and visit You should be viewing a very similar page to the original except there is a toolbar located along the bottom of the page providing extra information. Symfony has very powerful debugging tools built right in to the framework itself.

Make a change to your template, save and reload. The changes are instantaneous with no need to clear any caches. Try taking the app_dev.php part out of the URL and reload the page, your changes have no effect yet as they are still using the cache.


  • Look at the Symfony\Component\HttpFoundation namespace for the various types of response available

Communication, our greatest tool

Whether you want to connect, have a problem I can help with or simply want to drop a line, I welcome your words!