Creating A Simple Website Using SensioLabs (Symfony) Silex and Twig
It’s been a long time since I last wrote a tutorial, all I can do is apologise for the long gap and tell you that it was mostly due to lack of time and, to be frank, lack of the motivation to write. Recently though I have found myself with a renewed vigour and I will be posting more tutorials as regularly as work allows.
What is Silex?
Silex is a PHP Micro Framework from SensioLabs, they are the same developers who gave us the fantastic Symfony PHP Framework that is well known and loved.
Silex is great for building small projects and today I’m going to show you how to build a simple website with routing and using Twig as a templating engine.
Getting Silex
To get started you will need to get Silex into your development environment. If you are using Vagrant, or you have Composer installed, you can just navigate to the folder you want to install Silex and create a composer.json
file with the following inside.
1 2 3 4 5 6 7 |
{ "require": { "silex/silex": "~1.1", "twig/twig": ">=1.8,<2.0-dev", "symfony/twig-bridge": "~2.3" } } |
After that you just need to run:
1 |
composer install |
If all that doesn’t make sense to you then you probably don’t want to use Composer, although I do recommend you check it out at some point, and instead want to download the archive from the Silex website. Please choose the Fat archive or you will be missing some of the dependencies required for this tutorial.
If you do download the archive, delete the web folder included with the archive. We will not be using that for this tutorial. DO NOT do that in a production environment. We are only doing that here to keep things simple as, depending on your setup, it can be complicated to keep Silex outside of the web root. We will look at how to do that in the second part of this tutorial.
Silex Demo
We all like a demo, so here is what we are going to be creating. If you like Llama’s you’ll love this.
Demo Images
The images I have used in the demo are all from Flickr and are licensed under Creative Commons. I have included them inside the Project Files zip, but if you are following along without the files please head to the demo and save them to your computer, or replace them with Kittens.
Including Silex
The first thing we need to do is create the one and only file that will contain PHP, the rest of the tutorial will be fleshing out the website with Twig template files and CSS. So in the folder you have extracted/installed Silex into create an index.php
file.
I’m going to go over the file in a few steps to help explain what sections of the code do & so that it helps break things up. First up, let’s setup Silex.
1 2 3 4 5 6 |
<?php require_once __DIR__.'/vendor/autoload.php'; $app = new Silex\Application(); $app['debug'] = true; |
That’s all there is to it. This just includes the autoloader for Silex and creates a new application, stored into the $app
variable.
Obviously you do not want to leave the debug flag in when running in production, but it provides a lot of detailed and useful information if we hit any errors during development. Just remember to comment it out or remove it when moving to a production environment.
1 2 3 4 5 6 7 8 9 10 11 12 |
$app->register(new Silex\Provider\TwigServiceProvider(), array( 'twig.path' => __DIR__.'/views', )); $app['twig'] = $app->share($app->extend('twig', function($twig, $app) { $twig->addFunction(new \Twig_SimpleFunction('asset', function ($asset) use ($app) { return sprintf('%s/%s', trim($app['request']->getBasePath()), ltrim($asset, '/')); })); return $twig; })); $app->register(new Silex\Provider\UrlGeneratorServiceProvider()); |
Next up we register Twig with our app. Twig comes built into the Fat version of Silex as a service so we can just ask Silex to enable it and we are good to go. We also pass a path to our views folder. I have chosen to make a folder called views. Please feel free to change this to anything you like.
We are also adding a handy function to twig which will generate the URL to an asset, that would be any images, CSS files or JS files. This is useful because it saves space in our template files, and if we wanted to we could host assets on a CDN and we would only need to make a small change to our function. For those curious you would do this:
1 2 3 4 5 6 |
$app['twig'] = $app->share($app->extend('twig', function($twig, $app) { $twig->addFunction(new \Twig_SimpleFunction('asset', function ($asset) use ($app) { return sprintf('http://external.assets.com/%s', trim($app['request']->getBasePath()), ltrim($asset, '/')); })); return $twig; })); |
Simple and retroactive.
Finally we register the URL Service provided in the Silex framework so we can generate links without any complications.
1 2 3 |
$app->before(function ($request) use ($app) { $app['twig']->addGlobal('active', $request->get("_route")); }); |
This adds a global variable that will be available to all Twig templates which contains the name of the route that is being displayed. This can be useful for many things, but in this case I am going to use it to mark the current page’s navigation link as active. More on that later.
$app->before()
is ran before the controller is executed and is ran regardless of the route being displayed. That’s why we are using it here, it is ran before the templating system has executed so we can add a global variable to Twig without any issues.
1 2 3 4 5 |
$app->get('/', function() use ($app) { return $app['twig']->render('layout.twig'); })->bind('home'); $app->run(); |
Here we are starting to add the page routes. We’ll just go through the home page to start with, that way we don’t overload on information. For the moment we are just adding the index route and the run command.
This is pretty simple. We are creating a route for the path /
. This would refer to the root of your domain, or if you were hosting from a sub-folder (like my demo) that would become the root. That is provided your Apache/Nginx config is set-up correctly, more on that in the tips at the end of this tutorial.
We simply pass the $app
variable through to our function using the new use
keyword introduced in PHP 5.3.0 (I believe). This allows us to use the Twig render and return it. Returning while in the context of a route will output to the browser. bind()
gives the route a name so we can generate links to it using the URL Generator service later.
Introducing Twig
If you have never used Twig before prepare for a treat. Twig is a wonderful templating engine, I much prefer it to Smarty. It reminds me of Handlebars (a JS based templating engine) which is a good thing.
First, if you didn’t already, create a folder in your project root named the same as the name you gave the twig.path
option when registering the Twig service with Silex. Remember? I called mine views
. In that folder create a new file called layout.twig
. Yes. twig
is the file extension. You can use a normal HTML extension instead if you wish, but this extension helps remind me they are twig files.
In this file you need to create the layout or the common HTML code that all our pages will use. This is going to be a big code block, but here it is.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
<!DOCTYPE html> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>My Awesome Site</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"> <link href="//maxcdn.bootstrapcdn.com/bootswatch/3.2.0/darkly/bootstrap.min.css" rel="stylesheet"> <link href="{{ asset('css/styles.css') }}" rel="stylesheet"> <!-- Place favicon.ico and apple-touch-icon.png in the root directory --> </head> <body> <div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="{{ path('home') }}">L&LLF</a> </div> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li{% if active == 'home' %} class="active"{% endif %}><a href="{{ path('home') }}">Home</a></li> </ul> </div><!--/.nav-collapse --> </div> </div> <div class="container"> {% block content %} <div class="starter-template"> <p><img class="img-responsive" src="{{ asset('img/llama-loaf.jpg') }}" alt="Image 'Llama Loaf' by Ian Sane"></p> <h1>Breeders. Shearers. Welsh.</h1> <p class="lead">Come visit our luxurious Llama farm deep in the Welsh valleys.<br> See our magnificent Llama Spa where our Llamas chill before their big shows.</p> <p><small>Image 'Llama Loaf' by <a href="https://www.flickr.com/photos/31246066@N04/" title="Ian Sane on Flickr">Ian Sane</a></small></p> </div> {% endblock %} </div><!-- /.container --> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> </body> </html> |
That’s a lot of code, but the bulk of it is just a standard HTML document with Bootstrap attached via the BootstrapCDN.
First a quick note about Twig. In Twig using {{ }}
tells Twig to echo the contained variable or the returned result of the enclosed function. Using {% %}
tells Twig to expect logic (if…else etc) instead.
Let’s go through some of the unusual code. First up is our link tag.
1 |
<link href="{{ asset('css/styles.css') }}" rel="stylesheet"> |
You may remember we created a helper called asset
in our Silex code. This is it in use. We just pass the path (relative to root) to our CSS file and the function converts it to a absolute URL. Remember this is just for local content or content on your CDN. If you are linking off-site just enter the full URL as you would normally.
Next up is the path()
helper.
1 |
<a class="navbar-brand" href="{{ path('home') }}">L&LLF</a> |
This produces a relative path (from root) to the route named home
. Remember? We used the bind()
method on our route to give it a name.
1 |
<li{% if active == 'home' %} class="active"{% endif %}><a href="{{ path('home') }}">Home</a></li> |
Remember when we created our index.php
file? We created a global variable for Twig called active
this is why. That variable contains the currently active route, so we can use that to set the currently active nav button.
All we do is a quick if
using Twig to check if the current link should be marked as active. Same applies for any other nav links added in the future, but with the route name changed. You could automate this by creating a service, but I wanted to keep it fairly simple & it isn’t much effort to copypasta then change three words.
Finally is the main chunk. The block
.
1 2 3 4 5 |
{% block content %} <div class="starter-template"> ... </div> {% endblock %} |
This creates a block that can then be overridden in any other Twig template that extends this one. More on that when we create our next page. The content inside will be shown if the template is displayed & no other template overrides that particular block. Because of that I have used it to display the home page content.
That’s all for the layout.twig
file. Let’s move onto the CSS before we create any more pages.
Minimal CSS
Create a folder called css
in your project root & then create a file called styles.css
. Inside all you need to replicate my demo site is this:
1 2 3 4 5 6 7 |
body { padding-top: 50px; } .starter-template { padding: 40px 15px; text-align: center; } |
I wanted to keep the focus on building the site so it uses as much of Bootstrap’s default styling as possible. These are just two tweaks needed to add spacing in.
Taking Stock
Now seems like a good place to pause and look at what we have.
At this point you should have a home page built using Silex & Twig. You should be able to pull it open in your browser and see it in all its glory. Awesome, huh?
Adding About & Contact Pages
Now let’s add two more pages. First up we’ll add our About page. Let’s make our template first. Create a new folder inside your views
folder called pages
, inside that create a file called about.twig
. While this isn’t necessary it does helps us keep our pages separated from our layout.
Place this inside the new about.twig
file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{% extends 'layout.twig' %} {% block content %} <div class="starter-template"> <p> <img class="img-responsive" src="{{ asset('img/llama-farma.jpg') }}" alt="Llama Farmer? No problem!"> </p> <p class="lead">Llama farmer? No problem, add your best llama pic above!</p> </div> <div class="row"> <div class="col-md-8"> <h2>Who Are We?</h2> <p>We are Lily & Larry's Llamas. Llama farmers from Llangynidr in Wales.</p> <h2>What Are Your Plans?</h2> <p>To breed, show & shear award winning Llamas with bows in their fur.</p> <h2>This Is Madness!</h2> <p>That is because this is a demo, none of this content matters. It is all just nonsense.</p> </div> <div class="col-md-4"> <h3>Is This A Sidebar?</h3> <p>Yes... Yes, it is. You could add more Llama based links here.</p> </div> </div> {% endblock %} |
This is pretty simple. The {% extends 'layout.twig' %}
tells Twig that this file will be used in addition to our original layout.twig
file. Now thanks to that we can re-define any blocks located in that file to insert new content. As you can see we redefine the content
block here.
Now let’s create our contact template too. In the pages
folder again, create contact.twig
. Then fill it with this content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
{% extends 'layout.twig' %} {% block content %} <div class="starter-template"> <h1>Contact Us</h1> <p class="lead">Want some Llama farming tips? Get in touch using the form below.</p> </div> <div class="row"> <div class="col-md-4"> <p><img class="img-responsive" src="{{ asset('img/long-llama.jpg') }}" alt="Image 'Llama' by Asra Valorshon"></p> <p class="text-center"><small> Image 'Llama' by <a href="https://www.flickr.com/photos/26548797@N00/" title="Asra Valorshon on Flickr">Asra Valorshon</a></small></p> </div> <div class="col-md-8"> <p>Contact us using the form below and we'll get back in touch with you when we aren't shearing our luscious Llamas.</p> <form role="form" method="get" action="{{ path('home') }}"> <div class="form-group"> <label for="name">Name</label> <input type="name" class="form-control" id="name" placeholder="Enter Name"> </div> <div class="form-group"> <label for="email">Email address</label> <input type="email" class="form-control" id="email" placeholder="Enter email"> </div> <div class="form-group"> <label for="message">Message</label> <textarea name="message" id="message" class="form-control" rows="3" placeholder="Enter Your Message"></textarea> </div> <div class="form-group"> <button type="submit" class="btn btn-default">Send It</button> </div> </form> </div> </div> {% endblock %} |
There is nothing new in this file in regards to Silex or Twig. The contact form does not work in this tutorial, but I will be making a second part very soon showing you how to get it working.
Now one final change to a template. We need to add in two extra links to our Nav bar. Open up layout.twig
and add these under the home link.
1 2 |
<li{% if active == 'about' %} class="active"{% endif %}><a href="{{ path('about') }}">About</a></li> <li{% if active == 'contact' %} class="active"{% endif %}><a href="{{ path('contact') }}">Contact</a></li> |
So you should now have:
1 2 3 4 5 |
<ul class="nav navbar-nav"> <li{% if active == 'home' %} class="active"{% endif %}><a href="{{ path('home') }}">Home</a></li> <li{% if active == 'about' %} class="active"{% endif %}><a href="{{ path('about') }}">About</a></li> <li{% if active == 'contact' %} class="active"{% endif %}><a href="{{ path('contact') }}">Contact</a></li> </ul> |
Now let’s add in the code to our index.php
file so Silex will create the pages. Under your home
route, add the following.
1 2 3 4 5 6 7 |
$app->get('/about', function() use ($app) { return $app['twig']->render('pages/about.twig'); })->bind('about'); $app->get('/contact', function() use ($app) { return $app['twig']->render('pages/contact.twig'); })->bind('contact'); |
That’s it. All done.
If you visit your project you should have an exact duplicate of the site shown in my demo. Well except for the images, if you swapped them out for kittens.
Silex Tips
Some tips I have come across. First up is server configuration. There is a pretty extensive guide over at the Silex documentaion. I’m running Nginx and my demo is using a slight modification of their configuration without any issues at all. Even with WordPress running along side it.
Contact Form
The contact form was left in-active in this tutorial. I wanted to save something so we could revisit how to improve on this project. So I’ve decided to split it out into a second part, it also stops this tutorial from becoming way too long. Look out for it very soon.
Silex Location
You may have noticed that I have placed Silex inside the web accessible root. This is not recommended, but is the way most (who are starting out) seem to approach things. That is why I wanted to go that way. In the next part we will also look at how to move Silex to a more secure location outside the web accessible root.
Conclusions
I hope you have enjoyed our look at Silex & Twig. This tutorial was just a look at how to create a simple website. The idea was to look at how to get started using both Silex & Twig. From there you can add as much or as little to it as you want.
If you have any questions or issues please get in touch via the comments & I’ll try to help out as much as I can.
10 Comments
Salih
Hi, first of all thank you very much for this tutorial, I am also started learning SILEX, and was in search for an up and running tutorial. As far as now it is good, but looking for the main part, like Forms, using silex form service, validtor, swiftmailer service and of course THE MAIN AJAXIFYING the forms. So hope you will do these stuffs in upcoming parts(mainly ajax part). Cheeers
Paul Robinson
Hi,
Glad the tutorial was helpful. I have done a second part showing how to integrate SwiftMailer with the form, but it sends the form via POST rather than POST via AJAX.
I will be adding a third part soon showing how to add the Validator & using the Form service. If it would be of interest I can add in how to send a form over AJAX too.
Maksym Minenko
To Paul Robinson.
Any progress on that third part? 🙂
Jatniel
Hi!
Thanks for this guide, which is very good.
I have a question:
the guide and the example “llama” , the links returns a 404 error. How do I fix this error?
I have to set up or add to .htaccess?
Thanks 😉
Jatniel
Solution:
Admin edit: Added in pre/code tags to make things look prettier.
Paul Robinson
Hi Jatniel,
Glad you managed to find a solution. As mentioned in the tutorial (under Silex Tips), I didn’t cover server setup as there is a great guide on the Silex website that covers common configurations for Apache, Nginx, IIS, and Lighttpd. If another visitor is looking for it and missed it in the article you can find it on the Silex website.
Jatniel
Hi Paul,
Thanks! 🙂
Eka
This is so brief yet complete explanation about routing, templating, and asset management. Thanks!
I’m going to the next post about form now 😉
Paul Robinson
Hi Eka,
Glad it was helpful. Hope you find/found the other parts just as helpful.
Naj
Great Article! Very helpful. Hope you’ll provide more tutorials about Silex.