Building a small website with Silex

Silex

Objectives

I want to:

  • Build a small website and have access to some basic features such as restful routing without having to use a full-fledged framework

Composer Initialization

In this article, we'll cover the initial setup of a Silex project, which we'll build as we go along. If you need more information about composer, check out our article on the subject.

We'll start out with creating a myapp folder, which will be our working directory.

Let's initialize our project via composer:

$ composer init
  Welcome to the Composer config generator
This command will guide you through creating
  your composer.json config.

Package name (vendor/name) [root/myapp]: lti/myapp
  Description []: A small website using Silex
  Author [Admin admin@linguisticteam.org]:
    Minimum Stability []: stable
    License []: MIT

    Define your dependencies.

    Would you like to define your dependencies
    (require) interactively [yes]?
    Search for a package []: silex/silex
    Enter the version constraint to require (or leave blank
    to use the latest version) []:
    Using version ~1.2 for silex/silex
    Would you like to define your dev dependencies 
    (require-dev) interactively [yes]?
    Search for a package []:

    {
    "name": "lti/myapp",
    "description": "A small website using Silex",
    "require": {
    "silex/silex": "~1.2"
    },
    "license": "MIT",
    "authors": [
    {
    "name": "admin",
    "email": "admin@linguisticteam.org"
    }
    ],
    "minimum-stability": "stable"
    }

    Do you confirm generation [yes]?

We added silex right off the bat by using the composer init command, but we could have done so later by editing the dependency property in composer.json or by executing:

$ composer require silex/silex

Let's pull in our silex packages:

$ composer install

As you'll notice, silex comes with baggage, let's run briefly through the dependencies:

  • symfony/routing: manages those restful, clean urls we're so fond of
  • psr/log: lays down a "common language" that logging libraries can share
  • symfony/http-foundation: the heart and soul of the web application. Allows us to manipulate developer friendly requests and generate equally friendly http responses
  • symfony/event-dispatcher: used by the Application class to allow us to hook into our application lifecycle at various points
  • pimple/pimple: pimple is the service container that allows us to stack so much functionality on top of our Application class, which extends pimple.
  • silex/silex: the namespace for all the goodies described above.

Now that we have the brains of our operation, let's build some muscle.

Running the app

We're going to create a public folder which will be the root of our site. We create a file called index.php with the following code:

	//Allowing composer to load all our dependencies on its own.
	require_once __DIR__.  '/../vendor/autoload.php';

	$app = new Silex\Application();

	$app->get(  '/', function () use ($app) {
	 return "We're up and running!";
	});

	$app->run();

Half a dozen lines of code, and we're up and running! Ain't that a beaut'? The Silex usage page should tell you most of what you need to know to build some base functionality.

Service Providers

In the rest of this article, we'll run briefly over some service providers you may find useful. You'll find "real world" applications of the providers described below in the LTI frontend github project. The LTI_Frontend class is where the initialization magic happens.

ServiceController

As routes start piling up on your site, you may find it useful to have a special place in your project where controller work is done. The service controller service provider allows us to load our controllers lazily (only when needed), depending on which routes are served.

We start out by registering the provider in index.php:

$app->register(
  new Silex\Provider\ServiceControllerServiceProvider()
);

We instantiate our first controller as a service in our application:

$app['controller.home'] = $this->app->share(
  function () use ($app) {
      return new HomeController($app);
  }
);

We isolate our controller in its own file called HomeController.php and have it do normal controller stuff:

use Silex\Application;
class HomeController extends Controller {
  public function index(){
    return "Congrats, you just used the right controller.";
  }
}

We have our HomeController extend a Controller class which we also created, in case we want to add properties or methods that will be shared across controllers.

We register our route:

$app->get('/', "controller.home:index");

Browsing to the root of our website should trigger the home route and its associated controller, HomeController.

Twig

With our half decent controllers wired up, we need some way to display our content in proper html. Twig comes to the rescue! Twig is a template engine which despite being lightweight has all the features you'd expect out of a template manager.

Before we bring twig into our application, we need to add it as a dependency in composer:

$ composer require symfony/twig-bridge

We typically store our layout in a views folder, which is the location we'll provide as a parameter when we register the service provider in index.php:

$this->app->register(
  new Silex\Provider\TwigServiceProvider(), array(
    'twig.path' => __DIR__ . '/../views',
  )
);

In our parent Controller class, we stored our twig environment into a variable to lighten our syntax:

$this->view = $app['twig'];

We can now modify our controller so it renders a twig template:

use Silex\Application;
class HomeController extends Controller {
	public function index(){
	    return $this->view->render('home.twig');
	}
}

home.twig will contain all the HTML, CSS and javascript that'll make our page pretty.

UrlGenerator

How about referring to pages as route names instead of their full url? Let's see what Silex's UrlGeneratorServiceProvider can do for us. Let's start by registering it in index.php:

$app->register(
  new Silex\Provider\UrlGeneratorServiceProvider()
);

We need to name our routes so the URL generator knows what we're referring to when we try to generate urls. We do that by using the bind() method when we register our route:

$app->get('/', "controller.home:index")->bind('home');

Since we've already registered the twig service provider, we have access to the url generator in our twig templates:

<!--Generating a url in a twig template
thanks to UrlGeneratorServiceProvider -->
{{ app.url_generator.generate('home') }}
<!-- or -->
{{ url('home') }}

Translator

The TranslationServiceProvider comes in handy to render pages in multiple languages. Let's bring in the dependency:

$ composer require symfony/translation

and register the service provider:

$app->register(new TranslationServiceProvider());

The translator provider allows us to pull in translations in a variety of ways, and we chose XLIFF files for the sake of our example. XLIFF files use the XML format with a namespace that was specifically designed to handle translations.

We're going to set up a language file for English with a key for each string we want to translate and its translation as it should appear on our webpages. Since our language file is in English only, we haven't set a target language.

We put this file called home.xlf into an "en" folder and we'll add one file per page we want translated.

Our translation file looks like this:


  
  
      
      
        
        navbar.home
        Home
      
      
        
        navbar.products
        Products
      
      
        
        navbar.about
        About us
      
      
    
  

When we're done creating our English language files, we can copy paste those files into, say, a folder for French translations named "fr". Our example XLIFF file would look like this:


  
    
      
      
        
        navbar.home
        Accueil
      
      
        
        navbar.products
        Produits
      
      
        
        navbar.about
        À propos
      
      
    
  

Our translated files will be loaded depending on the user's locale:

$locadedLocale = $app['translator']->getLocale();
$app['translator']->addResource(
   'xliff',
    __DIR__ . '/../locales/' . $loadedLocale . '/home.xlf',
   $loadedLocale
);

Now that the translation file is loaded, we can use our translated strings by using a translation method either in php:

$app['translator']->trans('navbar.home');

or in our twig templates:

{{ app.translator.trans('navbar.home') }}

Monolog

In a development environment, we're usually in debug mode so errors are displayed on screen. In a production environment however, we'll need a logging mechanism that will allow us to get some feedback from our web app. When it comes to logging in the PHP world, it's hard not to come across the awesome Monolog library, which we'll use as part of the MonologServiceProvider:

$ composer require monolog/monolog

When registering the service provider, we provide the path where Monolog will store our logs, and for the sake of our example, we'll set the logging level at warning, which means we'll only log events that are flagged as warning or error. Internally, Silex sets up an event listener that will trigger the default handler when monolog methods are called.

$this->app->register(new MonologServiceProvider(), array(
    'monolog.logfile' => __DIR__ . '/../logs/silex.log',
    'monolog.level' => Logger::WARNING,
));

As usual with utilities that write on filesystems, we have to make sure that proper access permissions have been defined on the production server so logs can be written properly.

Now that monolog is registered in our service container, we can just summon the beast whenever we need it:

$app['monolog']->addWarning("There's a snake in my shoe!");

That method will trigger monolog to write
[yyyy-mm-dd hh:mm:ss] myapp.WARNING: There's a snake in my shoe! in logs/silex.log.

Conclusion

Besides being a solid framework for building small scale projects, Silex is a great gateway to the mighty Symfony and allows developers to familiarize themselves with Symfony without having to face the framework full on. Another benefit for beginners is the opportunity to develop a workflow with composer, whose foothold in the PHP community grows by the day. Might as well get on the train now!