Scaling Silex applications


In my humble opinion Silex is great. It’s perfect to create prototypes, but when our application grows up it turns into a mess. That was what I thought until the last month, when I attended to a great talk about Silex with Javier Eguiluz. OK. Scaling Silex it’s not the same than with a Symfony application, but it’s possible.

It’s pretty straightforward to create a Silex application with composer:

{
    "require": {
        "silex/silex": "1.0.*"
    },
    "minimum-stability": "dev"
}

But there’s a better way. We can use the Fabien Potencier’s skeleton. With this skeleton we can organize our code better.

We also can use classes as controllers instead of using a closure with all the code. Igor Wiedler has a great post about this. You can read it here.

Today I’m playing with Silex and I want to show you something. Let’s start:

Probably you know that I’m a big fan of Symfony’s Dependency Injection Container (you can read about it here and here), but Silex uses Pimple. In fact the Silex application extends Pimple Class. My idea is the following one:

In the Igor’s post we can see how to use things like that:

$app->match('/video/{id}', 'Gonzalo123\ApiController::indexAction')->method('GET')->bind('video_info');

My idea is to store this information within a Service Container (we will use Symfony’s DIC). For example here we can see our routes.yml:

routes:
  video_info:
    pattern:  /video/{id}
    controller: Gonzalo123\ApiController::initAction
    requirements:
      _method:  GET

As we can see we need to implement one Extension for the alias “routes”. We only will implement the needed functions for YAML files in this example.

<?php

use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class SilexRouteExtension implements ExtensionInterface
{
    /**
     * Loads a specific configuration.
     *
     * @param array            $config    An array of configuration values
     * @param ContainerBuilder $container A ContainerBuilder instance
     *
     * @throws InvalidArgumentException When provided tag is not defined in this extension
     *
     * @api
     */
    public function load(array $config, ContainerBuilder $container)
    {

    }

    /**
     * Returns the namespace to be used for this extension (XML namespace).
     *
     * @return string The XML namespace
     *
     * @api
     */
    public function getNamespace()
    {

    }

    /**
     * Returns the base path for the XSD files.
     *
     * @return string The XSD base path
     *
     * @api
     */
    public function getXsdValidationBasePath()
    {

    }

    /**
     * Returns the recommended alias to use in XML.
     *
     * This alias is also the mandatory prefix to use when using YAML.
     *
     * @return string The alias
     *
     * @api
     */
    public function getAlias()
    {
        return "routes";

    }
}

And now we only need to prepare the DIC. According to Fabien’s recommendation in his Silex skeleton, we only need to change the src/controllers.php

<?php

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

// Set up container
$container = new ContainerBuilder();
$container->registerExtension(new SilexRouteExtension);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../config/'));
// load configuration
$loader->load('routes.yml');
$app['container'] = $container;

$app->mount('/api', include 'controllers/myApp.php');

$container->compile();

$app->error(function (\Exception $e, $code) use ($app) {
    if ($app['debug']) {
        return;
    }

    $page = 404 == $code ? '404.html' : '500.html';

    return new Response($app['twig']->render($page, array('code' => $code)), $code);
});

and now we define the config/routes.yml

routes:
  video_info:
    pattern:  /video/{videoId}
    controller: Gonzalo123\ApiController::initAction
    requirements:
      _method:  GET

And finally the magic in our controllers/myApp.php:

<?php

$myApp = $app['controllers_factory'];

foreach ($container->getExtensionConfig('routes')[0] as $name => $route) {
    $controller = $myApp->match($route['pattern'], $route['controller']);
    $controller->method($route['requirements']['_method']);
    $controller->bind($name);
}
return $myApp;

The class for this example is: src/Gonzalo123/ApiController.php

<?php

namespace Gonzalo123;

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\JsonResponse;

class ApiController
{
    public function initAction(Request $request, Application $app)
    {
        return new JsonResponse(array(1, 1, $request->get('id')));
    }
}

As you can see the idea is to use classes as controllers, define them within the service container and build the silex needed code iterating over the configuration. What do you think?

22 thoughts on “Scaling Silex applications

  1. An alternative approach would be to _not_ use the Symfony2 DIC, but instead use the `YamlFileLoader` from the Routing component.

    Like this:

    use Symfony\Component\Config\FileLocator;
    use Symfony\Component\Routing\Loader\YamlFileLoader;

    $app[‘routes’] = $app->share($app->extend(‘routes’, function ($routes, $app) {
    $loader = new YamlFileLoader(new FileLocator(__DIR__.’/../config’));
    $collection = $loader->load(‘routing.yml’);

    $routes->addCollection($collection);

    return $routes;
    }));

    It can just load the routes into the RouteCollection directly instead of going through silex.

  2. RouteCollection from a YAML file. I didn’t know that it could be done! Sound good, really good. I will study it a little bit, but it opens a huge window in my mind working with Silex. Thanks!

  3. that’s definetly great , i was looking for configuring my silex controllers with something like that.
    Would it be possible to configure all pimple ( with the service providers , etc … ) that way ?

    1. I asked myself this question too. One Silex clone built over sf’s DIC instead of Pimple. Maybe the answer is simple: Symfony2. Probably after a huge work we will get one small version of sf. Not sure if it’s a good Idea. I want to work with idea of the controllers collections from YAML idea first.

    1. Yes. Igor’s answer is the best part (this guy knows a little bit about Silex :)). I need to change the post according to his recommendations.

  4. A question , if you go down that route , how do you set ut before/after events to a route ? seems you cant do it in the yml file. Also it would be cool if i could declare pimple dependencies in a yml file ( simple functions and service providers ).

    1. It can be done but we need to code :). It’s only a proof of concept. To set up before and after eventes we need to change controllers/myApp.php with the information about them.

      Probably the RouteCollection + YAML. I need to check it out

      1. I fond a solution : there are some options you can use to set up before/after callbacks :
        for instance :
        options:
        _before_middlewares: [ Controller\DinnerController::before ]

        would work if before is a static method on the DinnerController class , so one can configure all it’s app with yml or xml file. still trying to use annotations too , but it is perfectly possible i think !

  5. Another alternative is to use the full routing component, which will add caching and loading from resources such as xml and json.

    This is what Flint does http://github.com/henrikbjorn/Flint

    Also it make $this->app available for your controllers so they dont need to typehint it all the time (like controllers have $this->container in Symfony Standard Edition).

  6. I would like to share with you a project I’ve made.
    http://bit.ly/silex-simple-rest

    It’s a simple but scalable skeleton for building REST api.

    It supports CORS of the box.

    The project is also fully tested, and use SQLite as default datastore, but you can simply implements your own desired datastore.

    Please let me know what do you think about it.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.