PHP Like a Pro: Part 7 (First Steps with Silex)

As I mentioned in the previous installment, I want to use Sixex four our new issue management tools. Why? Because it is easy to work with, lean, powerful and very testable. My composer.json file at the moment looks like this:

{
    "require": {
        "silex/silex": "1.0.*",
         "twig/twig": ">=1.8,<2.0-dev"
    },
    "minimum-stability": "dev",
    "autoload": {
        "psr-0": {
            "SITS":       "src/"
        } 
   } 
}

I think I put up a basic “Hello World” example last time, but I will do it once again for the sake of completeness. Here is a basic Silex App, but keep in mind that to get it working you need to include a few rules in your .htaccess file. I illustrated this in Part 3 of this series.

<?php require "vendor/autoload.php";
 
// front controller
$app = new Silex\Application();
 
$app->get('/', function() use($app) {
    return 'Hello World';
});
 
$app->run();

Yep routing in Silex is that simple. You use either a get or a post method, you specify the address pattern and you return a string that gets rendered on the page. Its very elegant and what is more important it enforces good habits. For example, when writing a Silex application there is absolutely no reason for you to ever have to use a print statement. Compare it to our pastebin where we would have to manually render our twig templates by printing them out with echo.

With Silex we can simply hand off that job to the framework. We simply build a string, and pass it on without caring what happens next. And the good news is that we can still use Twig just as easily if not easier than before. As a matter of fact it happens to be one of the built-in service providers. If I wanted to re-implement the above example using Twig templates I would simply have to add two lines of code:

<?php require "vendor/autoload.php";
 
// front controller
$app = new Silex\Application();
 
// set up the twig provider
$app->register(new Silex\Provider\TwigServiceProvider(), array(
    'twig.path' => 'views',
    'twig.autoescape' => true ));
 
 
$app->get('/', function() use($app) {
    return $app['twig']->render('main.html', array('name' => "<strong>World!</strong>"));
});
 
$app->run();

This will render the main.html file just as before. Note the register method. This is sort of how things are done in this framework. Your $app is the central point of the entire application and also a sort of a universal wrapper. You plug services and providers into it, and then you can just pass that one object into the routing closures. This keeps the code concise – when you define a routing closure (like we just did above for the / path) you just say use($app) and you are done. That one little variable extends it’s virtual tendrils throughout the entire code base and makes other things accessible in places where they otherwise wouldn’t be.

Granted, you could achieve something very similar by simply using a facade pattern, and defining bunch of static factories that would be globally accessible from any scope but… Well, sometimes you want to unit test. As the name suggests the main purpose of unit testing is testing units or components in isolation. You want to see how it behaves against some know, hard coded sets of inputs and outputs without interference from other parts of the code base. Unfortunately few functions or classes can exist in programmatic void, so it is a common practice to mock up fake objects and helpers for the purpose of tests. The problem with static factories is that they are not easy to fake.

You can see this in my test suite for the Pastebin app. In Part 3 I decided to create a TwigFactory class to avoid passing around a Twig environment object, and from that point I was mostly stuck with it. Thankfully the class was only few a lines long convenience helper, so it didn’t foul up my tests too much. But imagine if this was something more complex. Something with few thousand lines and an array of bugs of its own. I would be hard pressed not to include it in my tests. The Silex way gives you a live object you pass around your app, which can be easily mocked with canned set of responses for the purpose of testing.

Our Pastebin didn’t have any authentication logic, but the Issue Tracker ought to have one. It’s probably a good idea to get that bit out of the way first before we start building the rest of the site. Typically you handle this sort of thing using PHP’s global $_SESSION array and the collection of functions that initialize and flush it. It is a rather fiddly process – you have to remember to call session_start at the top of your scripts, make sure you properly unset the session variables when the user logs out and etc.

Silex provides us with something called SessionServiceProvider which is a helper class that gets registered within your $app much like we did with twig. Once you have it registered you can use it like this:

// session provider
$app->register(new Silex\Provider\SessionServiceProvider());
 
$app->get('/', function() use($app)
{
    // check if logged in
    if (null === $user = $app['session']->get('user')) return $app->redirect('/login');
 
    return $app['twig']->render('main.html', array('name' => "<strong>Luke</strong>"));
 
});

The session is maintained automagically. You don’t have to do anything. Want to log user out? Just do set(‘user’, null) and you’re done. It doesn’t get any simpler than that. Also note how easy it is to redirect. Compare this to the usual PHP way of accomplishing things which was always to send a custom header to the browser like:

 header( 'Location: /some/page' );

Yeah, sending raw headers to the browser as a standard recommended practice. This is one of those terribad, awfully misguided things you can learn when you browse PHP.net documentation sometimes. Of course there are legit reasons for the header function to exist, but doing redirects is not one of them.

Also, I would like to point out one more thing – I grabbed the authentication snipped directly from the Silex example and I would like to point out that I really approve of style. Let me re-post this line just so I can talk about all the things that are absolutely right here:

if (null === $user = $app['session']->get('user'))

First, note the tipple equals identity comparison operator which no one ever remembers to use in situations like this. Second, note the unconventional assignment order. Most programmers prefer to put variable on the left, and value on the right. It’s mostly a force of habit I guess, and one that is worth breaking. Why? Think about it – if you put the value on the left and variable on the right, and then somehow turn == to = you get a syntax error that can be fixed right away. If you do the same with the usual positioning, the compiler happily assumes you are doing an assignment, and keeps chugging along and you end up with a weird logical bug that will be much more difficult to find.

Finally, since in PHP assignment has higher precedence than identity comparison this will initialize $user and make it available within the scope of our routing closure. This is a lot of work accomplished by a single, concise and well written line.

How do you handle POST requests in Silex? It’s equally easy. Let’s say I make a login form that submits to itself via POST. Here is how I would capture that:

use Symfony\Component\HttpFoundation\Request as Request;
$app->post('/login', function(Request $request) use($app)
{
    $usr = $request->get('username');
    $pas = $request->get('password');
 
    //TODO: authenticate
 
    $app['session']->set('user', array('username'=> $usr));
    return $app['twig']->render('postLogin.html', array('username' => $usr));
 
});

Few words about first line in that snippet. Silex borrows a lot of modular components from the Symphony framework. The Request object is one of them. To effectively capture the POST request we have to use hinting in the function declaration. Without the type hint Silex gets a bit confused and bugs out. Since Symphony uses a deeply nested namespace scheme this gets kinda ugly, so I usually put that line somewhere in my index.php and then never think about it again.

My index file is getting a little bit crowded and top heavy. The pattern with Silex is to register services and providers ahead of time, so this will only get worse with time. Perhaps it would be a good idea to move all that declarative stuff to it’s own little place. I know I just said that static factories are not the best idea with respect to testing but I figured making a stating bootstrap function was the best way to encapsulate the initialization of my Silex app without polluting the code with dirty include statements. The bootstrap function will basically just set up Silex environment and return a fully initialized $app so that I can simply do $app = SITS\App::bootstrap(); at the top of my index.php file. This way my index file can contain play the role of the front controller doing all the routing logic without a lot of cruft on top.

The bootstrap will look like this:

<?php namespace SITS;
 
/**
 * Silex App 
 * 
 * @copyright 2013 Lukasz Grzegorz Maciak
 * @author Lukasz Grzegorz Maciak (maciak.net) 
 */
class App
{
    /**
     * Bootstrap the Silex application by registering all providers and
     * services and returning a pre-configured Silex Application object.
     * 
     * @static
     * @access public
     * @return \Silex\Application A Silxex app object
     */
    public static function bootstrap()
    {
 
        // front controller
        $app = new \Silex\Application();
 
        // ## Register Official Silex Providers ##
 
        // Twig provider for templating
        // twig.path is /views and autoescape ought to be on
        $app->register(new \Silex\Provider\TwigServiceProvider(), array(
            'twig.path' => 'views',
            'twig.autoescape' => true
        ));
 
        // session provider to handle user sessions
        $app->register(new \Silex\Provider\SessionServiceProvider());
 
        // ### Register Custom Services ###
 
        // functions dealing with password hashing
        $app['crypto.helper'] = function ($app) { return new Helper\Crypto(); };
 
        return $app;
 
    }
}

Note the second to last line of this class. This is how you define custom “services”. I mentioned this above, and here is a live example. I created a little Helper class called Crypto to help me handle hashing and salting passwords for our users. I will talk about its implementation in the next installment because this is a big topic, but I just wanted to show you how things get plugged into the $app variable.

At some point later I will be able to call the functions within the Crypto helper by doing:

$app['crypto.helper']->doSomething();

Before I wrap this up, here is my index.php for the sake of completeness.

<?php require "vendor/autoload.php";
use Symfony\Component\HttpFoundation\Request as Request;
 
$app = SITS\App::bootstrap();
 
// ## ROUTING STARTS HERE ##
 
$app->get('/', function() use($app)
{
    if(null === $user = $app['session']->get('user'))
        return $app->redirect('/login');
 
    return $app['twig']->render('main.html', array('name' => $user['username']));
});
 
$app->get('/login', function() use($app)
{
    return $app['twig']->render('getLogin.html');
});
 
$app->post('/login', function(Request $request) use($app)
{
    $usr = $request->get('username');
    $pas = $request->get('password');
 
    //TODO: authenticate
 
    $app['session']->set('user', array('username'=> $usr));
    return $app['twig']->render('postLogin.html', array('username' => $usr));
 
});
 
$app->get('/logout', function() use($app)
{
    $app['session']->set('user', null);
    return $app['twig']->render('logout.html', array());
 
});
 
$app->run();

My directory structure looks like this right now, just so you can see how things are spread out, and how I’m using the PSR-0 autoloading feature in Composer to it’s fullest extent:

SITS Project Directory

SITS Project Directory

Next time we will implement the Crypto helper, finish the login/logout logic and perhaps have a chance to do some other stuff as well.

This entry was posted in programming and tagged , . Bookmark the permalink.



2 Responses to PHP Like a Pro: Part 7 (First Steps with Silex)

  1. mike UNITED STATES Mozilla Firefox Linux says:

    Thanks for this post. Im barely starting out with Silex and this example was very helpful. Hopefully part 8 is coming sometime..?

    Reply  |  Quote
  2. Clops Mozilla Firefox Mac OS says:

    I have created a minimal silex template which could be a starting point for pretty much any web-site out of the box!

    Reply  |  Quote

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>