5 Reasons Why Silex Is King Of All PHP Micro-Frameworks

I want to be honest with you. PHP is not my favorite scripting language. Not at all. But every here and then, a new project comes around which keeps me in front of my computer for hours, rediscovering the joy (yes, joy) of writing PHP code. And one of those projects is Silex, a PHP 5.3 micro-framework, developed by Fabien Potencier and Igor Wiedler.

With Silex, you can build (smaller) web application with real minimal effort. It’s API is inspired by Sinatra’s DSL. Let’s kick off this article with a simple example taken from the Silex website:

require_once __DIR__.'/silex.phar'; 

$app = new Silex\Application(); 

$app->get('/hello/{name}', function($name) { 
  return "Hello $name"; 
}); 

$app->run();

Yep, that’s it! No fancy stuff, just a route, a “controller” and a simple “Hello World”-like response in just 6 lines of code.

Some of you might think: “Boooring! The idea of having a Sinatra inspired micro-framework isn’t new at all. There are plenty of others around. Why should I care for this stuff?”. You’re right. Besides Silex, there are projects like Limonade or Slim which offer a similar approach, but let me tell you what makes Silex the (micro-) king of all.

1. Quality codebase

If you’re looking for a PHP micro-framework with a sleek and modern codebase, look no further! Silex offers a sophisticated class library and makes heavy use of all those cool goodies that came with the release of PHP 5.3 like namespaces or lambda functions & closures. If you haven’t yet, you really should check out it’s code.

2. It’s based on the Symfony2 components

By the time I’m writing this article, we’re only one week away from the first public beta release of the Symfony2 web application framework, the successor of the famous symfony framework. While there’s still a lot to do before the first stable release, the big picture is already clear: Symfony2 isn’t just another MVC framework. Because if it’s flexible architecture, it’s more like some kind of meta-framemwork, a framework for building frameworks.

The Symfony2 core for example is based on several independent components for managing common tasks like request/response handling, routing or caching. The best thing about those components: They’re not tied to the framework itself. So, if you don’t want to reinvent the wheel, you could, or maybe should, clone them from Github and use them in your own PHP projects.

In fact, there are already a lot of open source PHP projects out there that were build using the Symfony2 components – and Silex is one of them. It’s probably the best foundation you could wish for a framework like this.

3. It’s got the coolest, tiniest DI container on the planet

(Hint: If you’re not familiar with dependency injection (DI), please refer to this article first)

If you take a look at the source code of the other micro-frameworks I mentioned above, you might notice one big problem: The use of global objects and even global functions, like in this example for Limonade:

// Example taken from the 'Limonade' docs
// http://www.limonade-php.net/README.htm

dispatch('/hello/:name', 'hello');
  function  hello()
  {
    $name = params('name');
    return 'Hello $name';
  }

That’s not only bad style, it’s death to testability.

Luckily, Silex offers a much better solution: A dependency injection (DI) container. The Silex application class extends from Pimple, a tiny DI container developed by Fabien Potencier. With only 125 lines of code (including comments!), it might be one of the smallest DI containers of all time, but, nevertheless, it does not need to hide from the other guys.

If you add services to the container, they’ll be stored for “lazy creation”, which means that they will only be created on demand. It goes even further by providing an API for shared services that will only be created once during a request.

Take the following example:

// inside bootstrap.php ...

// Add services to the DI container
$app['my_service'] = function() {
  return new MyService();
};

$app['my_shared_service'] = $app->share(function() {
  return new MySharedService();
});


// inside app.php ...

$app->get('/foo', function() use ($app) {

  // Requesting services
  $myService = $app['my_service']

  // Shared services are only created once so it doesn't matter
  // how often you request them - there will be only one shared instance
  $mySharedService = $app['my_shared_service'];
  $mySharedService = $app['my_shared_service'];
  $mySharedService = $app['my_shared_service'];
});

First, we register our two (imaginary) services my_service and my_shared_service are registered to the DI container. The Silex application class implements the \ArrayAccess interface, so we can treat it like an ordinary PHP array. By using to anonymous functions to “wrap” our services, we make sure that both will be created lazily, i.e. when we request our services from the container. As you can see, we made my_shared_service a shared service, so it will only be created once and then cached for later use.

It could be crucial for the performance of your web application to decide whether to have shared or non-shared service. Think of a more sophisticated service that is composed of many nested classes which might consume a lot of memory, or a remote service that has to establish a network connection on it’s creation. In both cases you should think about making it a shared service.

There are even more cool things about the DI container which would exceed the scope of this posting. The curious ones of you might want to check the corresponding section of the official Silex documentation.

4. The Extension API

If you use Silex out of the box, you’ll surely notice that it’s literally pretty much “naked”. No ORM, no templating engine, no session handling or anything else you know and love from your favorite full-stack web framework. After all it’s “just” a micro-framework, but it offers an API which makes extensibility ridiculously easy.

With the extension API, you can hook up your favorite PHP library in just a couple of minutes and make it available over the DI container. Take for example the Session extension from the official Silex extension repository:

namespace Silex\Extension;

use Silex\Application;
use Silex\ExtensionInterface;

use Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session;
use Symfony\Component\HttpKernel\Events as HttpKernelEvents;

class SessionExtension implements ExtensionInterface
{
    private $app;

    public function register(Application $app)
    {
        $this->app = $app;

        $app['session'] = $app->share(function () use ($app) {
            return new Session($app['session.storage']);
        });

        $app['session.storage'] = $app->share(function () use ($app) {
            return new NativeSessionStorage($app['session.storage.options']);
        });

        $app['dispatcher']->addListener(HttpKernelEvents::onCoreRequest, $this, -255);

        $app['session.storage.options'] = array();
    }

    public function onCoreRequest($event)
    {
        // ...
    }
}

And we’re done! Every Silex extension class has to implement the Silex\ExtensionInterface interface and provide a method named register where all the extension-magic happens. In the example above, the register method essentially bootstraps a session service and adds it to the DI container. Using this extension in your web application is pretty straight forward:

// inside bootstrap.php ...

// Register the session extension
$app->register(new SessionExtension());


// inside app.php ...

$app->get('/account', function() use ($app) {

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

  // ... 
});

Speaking of the official Silex extension repository: Here are the 10 official extensions which are already available, and I’m pretty sure there are still a lot more great ones to follow.

  • DoctrineExtension
  • FormExtension
  • MonologExtension
  • SessionExtension
  • SwiftmailerExtension
  • SymfonyBridgesExtension
  • TranslationExtension
  • TwigExtension
  • UrlGeneratorExtension
  • ValidationExtension

5. And boy, is it testable!

Why is testing important? It’s because modern web applications are highly dynamic. You add features, you refactor, you get rid of old stuff – you really don’t want to do all of that without testing, unless you enjoy spending your weekends on searching and fixing bugs.

Within other frameworks, testing might quickly become a real pain in the ass. And if the whole process becomes annoying for the developer, he might tend to disregard testing at all. There are some common design flaws in the PHP world that quickly can become a major problem when it comes to testing software:

  • Global objects and/or functions
  • Static methods
  • Singleton classes

Take this (simplified) example using symfony 1.x:

// ... inside userActions.class.php

public function executeRequestPassword(sfWebRequest $request) 
{
  $email = $request->getParameter('email');
  $user  = DoctrineCore::getTable('User')->findByEmail($email);

  if ($user)
  {
    $newPassword = PasswordHelper::generatePassword();

    $user->setPassword(                     
      CryptHelper::encrpytWithSha1($newPassword.$email)
    );
    $user->save();

    mail(
      $email,
      "Password reset",
      "Here's your new password: ".$newPassword
    );
  }
}

Let’s say we have an awesome web application which provides a password reset function for users who might have forgotten their password. We display a form where they can enter their email and after submitting the action above will be called. The action basically searches the user by his email, generates a new password, encrypts it, updates the user object and triggers a password reset email.

This seems to be ok at first sight but, man, this code smells really bad! Inside the action, there are three calls to static methods and last but not least, a global PHP function. Those four dependencies have been hard coded into the action, so if one of them had a bug in it, the whole action would blow. Then how do we test the action? The symfony documentation suggests writing functional tests for your actions:

$browser = new sfTestFunctional(new sfBrowser());
$browser->
  post('/requestPassword')->
  with('request')->begin()->
    isParameter('email', 'johndoe@example.org')->
  end()->
  with('response')->begin()->
    isStatusCode(200)->
    checkElement('div#sucess', 'A new password is on its way to you!')
  end()
;

The only way to test our action is though the user interface. That’s pretty suboptimal to say at least. What if the mail function would be called with wrong parameters? We’ll never know. And what if there’s a tiny little change in our success template? It might cause our test to fail, even if our actions works perfectly fine. No, that’s not what we want!

With Silex, we can eliminate (almost) every hard-coded dependency in our code thanks to the DI container:

$app->post('/request_password', function() use ($app) {

  $email = $app['request']->get('email');
  $user  = $app['db']->findByEmail($email);

  if ($user) {
    $newPassword = $app['helper.pw_gen']->generatePassword();
    $user->setPassword(
      $app['helper.crypt']->encryptWithSha1($newPassword.$email)
    );
    $user->save();

    $app['mailer']->send(
      $email,
      "Password reset",
      "Here's your new password: ".$newPassword
    );
  }

  // render template ...
});

Now we’re talking! No hard-coded dependencies. We could now easily replace services with mock objects for testing purpose (see below). Although Silex provides a dedicated class for functional / integration testing, we’re now even able to test our “controllers” with PHPUnit:

use Symfony\Component\HttpFoundation\Request;

class AppTest extends PHPUnit_Framework_TestCase
{
  public function setUp() {

    // ...
    $this->app = $app;
  }

  public function teardown() {

    // ...
    \Mockery::close();
  }

  public function testRequestPassword() {

    // Testing fixtures
    $phMock = \Mockery::mock('PasswordHelper')
      ->shouldReceive('generatePassword')
      ->withNoArgs()
      ->andReturn('abc123');

    $cryptMock = \Mockery::mock('CryptHelper')
      ->shouldReceive('encryptWithSha1')
      ->with('abc123'.$email)
      ->andReturn('fake-sha1-hash');

    $mailerMock = \Mockery::mock('AwesomeMailer')
      ->shouldReceive('send')
      ->with($email, 'Password reset', 'Here\'s your new password: abc123');

    $this->app['helper.pw_gen'] = $phMock;
    $this->app['helper.crypt']  = $cryptMock;
    $this->app['mailer']        = $mailerMock;

    $email = 'johndoe@example.org';

    $request = Request::create('/request_password');
    $request->set('email', $email);

    // Here we go!
    $response = $this->app->handle($request);

    $this->assertEquals(200, $request->getStatusCode());
  }
}

In case you’re wondering: Mockery is a sleek new PHP library for creating mock objects from existing classes. If you find the PHPUnit mocking API too clumsy and verbose, I recommend you to check out Mockery. It’s really good.

(By the way: Instead of writing functional tests, there’s now a much cooler way to test. Konstantin Kudryashov wrote an excellent BDD testing framework for PHP called Behat which was inspired by Cucumber. Together with Mink, it’s a really cool and easy way to test your web applications. And, of course, it plays nicely with Silex.)

Conclusion

I really could go on and on writing about how cool Silex is, but I don’t really want to bore you anymore for now ;) If my article finally convinced you, do yourselves a favor, hop over to the Silex website and try it out for yourselves. You will definitely not regret it. I’m planning to start a series of mini-tutorials for writing a full-blown web application with Silex in the near future. So, watch out for more very soon.

Filed under  //   frameworks   micro-framework   php   symfony   testing  

About

Addicted to shiny apples, beautiful pixels, awesome code and electronic music.

TwitterFacebookFlickr