Creating 'Hello World' using Silex 2 and Twig part two

This is part two of the tutorial series about setting up a simple website using Silex 2, Twig and PureCSS. In case you missed part one take a step back and read it. In this part we will render the pages using Twig and create a menu for browsing the site.

setting up Twig

Currently, we return a plain string, which is not very flexible, instead, we will be rendering our output using Twig templates. Using Twig gives us a nice separation of concerns, by letting Twig handle the structure and Silex passing through the content. First, we install Twig using composer, enter the following command in the docroot of the site:

composer require twig/twig

If all went well, you will have Twig installed, and since we already included the autoloader, Composer makes sure our app knows about Twig instantly. We can get started with defining Twig in our App, by adding the following line to out index.php file right after the line about debugging:

// Register the Twig service provider and let it know where to look for templates.
$app->register(new Silex\Provider\TwigServiceProvider(), array(
  'twig.path' => __DIR__.'/views',
));

As the comment says, we have now registered Twig and told it to look for templates in the /views directory, more about that in a minute. Now create the base template containing simple HTML and some placeholder content. Place the following code in /views/base.html.twig.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
      <title>Hello Kittens</title>
  </head>
  <body>
    {% block main %}
      <h1>Hello Kittens</h1>
      <p>This is a basic page using Twig.</p>
    {% endblock %}
  </body>
</html>

The Twig file consists mostly of plain HTML, displaying the page title, a header and a simple paragraph. I used a Twig block here, so we can override the body contents later. If you are not familiar with Twig, the twig commands for the structure are wrapped in {% %}, Twig code meant for outputting stuff are wrapped in {{ }}. With the {% block main %} {% endblock %} we wrap an area which will be rendered as is, unless we override it. Will see this overriding later, as we will be adding more pages. Important for now is that this 'base' template will be extended, with the 'main block' being overridable.

Go and visit the home page, and see is being rendered using out Twig template.

Adding more pages

First, we will add a Twig template for a page, which will extend the base Twig template. The page template does not render hardcoded string, but instead, it renders variables. Add the page.html.twig template in directory /views/pages:

{% extends 'base.html.twig' %}

{% block main %}
  {# If a title is set, render it. #}
  {% if title %}
    <h1>{{ title }}</h1>
  {% endif %}
  {# If content is set, render it. #}
  {% if content %}
    <p>{{ content }}</p>
  {% endif %}
{% endblock %}

We start our template file with telling Twig to extend the base.html.twig file using the twig extend command. Next in order to override the main block from the main.html.twig template, we add a Twig block with the HTML we want to see instead of the version from the main.html.twig template.

New in this file are the title and content variables, we pas these to Twig from our Silex app. I always check if the values in the variables exist, to avoid rendering empty HTML elements.

Now for the Twig part, we refactor the callbacks for our ''/more-kittens' and '/about' paths. There fore rewrite the callbacks for theses urls to let the look like the following snippet:

// Route for more kittens.
$app->get('/more-kittens', function () use ($app) {
    return $app['twig']->render('pages/page.html.twig', [
      'title' => 'More kittens',
      'content' => 'More kittens, still without images...',
    ]);
});

// Route to about page.
$app->get('/about', function () use ($app) {
    return $app['twig']->render('pages/page.html.twig', [
      'title' => 'About kittens',
      'content' => 'This is the about page.',
    ]);
});

When calling render() on the twig object within our app, a response object is created. We can simply return the response object back to the app, to render it in the browser. We are providing the Twig template file and an Array with variables to Twig. Twig uses the template file for rendering and exposes the passed variables for use within the template. This is a convenient way of passing values, which is provided by the Twig service provider (twig/twig which we installed at the beginning of part 2 using Composer). Strings rendered via Twig are escaped by default.

Associate the path with a name, for easy referencing

We have multiple pages, but no way of navigating them in a user-friendly way. To make this a nice developer experience, we will be giving the routes a friendly technical name called named route in Silex. We implement the bind() method, which we can chain to the get() method. In the code listing below you will see that we pass the developer friendly route name as a parameter to bind():

// Create route for the root of the site and return 'hello world'.
$app->get('/', function () use ($app) {
    return $app['twig']->render('base.html.twig');
})->bind('home');

// Route for more kittens.
$app->get('/more-kittens', function () use ($app) {
    return $app['twig']->render('pages/page.html.twig', [
      'title' => 'More kittens',
      'content' => 'More kittens, still without images...',
    ]);
})->bind('kittens');

// Route to about page.
$app->get('/about', function () use ($app) {
    return $app['twig']->render('pages/page.html.twig', [
      'title' => 'About kittens',
      'content' => 'This is the about page.',
    ]);
})->bind('about');

In order to be able to work with these named routes in Twig, we need to add the Symfony-Twig bridge bundle to our project. This bundle defines the path() function which will take a named route as a parameter, and renders this in a nice URL for us to use. Install the bundle using the following command from the root of our kitten's project:

composer require symfony/twig-bridge

We also edit the base Twig template, to have a list of menu items add the following below the <body> tag in base.html.twig:

<ul>
  <li><a href="{{ path('home')}}">home</a></li>
  <li><a href="{{ path('kittens')}}">more kittens</a></li>
  <li><a href="{{ path('about')}}">about</a></li>
</ul>

When you reload any of the pages of the Kittens site, you will see a list of links pointing to the three pages, feel free to test the links. It is also nice to rename a path in one of the files, so they do not match any more than reload and inspect the error message. When a route does not exist, Silex will throw an error, which is rather nice.

Up to now, we have been working toward a site rendering pages using Silex en Twig. You can follow along by checking out the tag for this part of the tutorial from Github. In part three we will be adding a visual layer using PureCSS.