Silex Controllers as Services

03 Oct 2012

**UPDATE**: The functionality described below is now baked in to Silex, checkout the [ServiceControllerServiceProvider](http://silex.sensiolabs.org/doc/providers/service_controller.html).
**TL;DR** Use a custom controller resolver to load controllers from the service container - [code][code]

I'll start by saying this is rarely the right thing to do with Silex. It's meant to be a micro framework, so if you're going to be building something that requires organising controllers in separate files or modules, maybe the full stack symfony framework would be a better choice. However, if you're like me and have gradually evolved a legacy application to be a Silex application, you may be in a situation where you want to try and keep things organised.

There's currently a pull request in the queue for Silex that adds a cookbook entry for [using controller classes][Cookbook], but I wanted to take it a step further and have my controllers as services, much like what's possible with the full symfony framework (See Richard Miller's post for further reading).

Things are best explained with an example, so let's start with this simple silex app:

<?php

use Silex\Application;
use Demo\Repository\PostRepository;

$app = new Application;

$app['posts.repository'] = $app->share(function() {
    return new PostRepository;
});

$app->get('/posts.json', function() use ($app) {
    return $app->json($app['posts.repository']->findAll());
});

First we need to tell Silex how we're going to map routes to our controllers. We do this by overriding the controller resolver with our own custom resolver. We can take the existing controller resolver and override the createController method. If we encounter a single colon in the controller name, we look to see if the string before the colon is a service, if it is, then we return a simple PHP callback array.

<?php

namespace Demo\Controller;

use Silex\ControllerResolver as BaseControllerResolver;

class ControllerResolver extends BaseControllerResolver
{
    protected function createController($controller)
    {
        if (false !== strpos($controller, '::')) {
            return parent::createController($controller);
        }

        if (false === strpos($controller, ':')) {
            throw new \LogicException(sprintf('Unable to parse the controller name "%s".', $controller));
        }

        list($service, $method) = explode(':', $controller, 2);

        if (!isset($this->app[$service])) {
            throw new \InvalidArgumentException(sprintf('Service "%s" does not exist.', $controller));
        }

        return array($this->app[$service], $method);
    }
}

We simply then override Silex's default resolver.

<?php
$app['resolver'] = $app->share(function () use ($app) {
    return new Demo\Controller\ControllerResolver($app, $app['logger']);
});

We can now convert our controller to be a class, add it to Silex as a service ready to be instantiated, complete with dependencies, when needed:

<?php

namespace Demo\Controller;

use Demo\Repository\PostRepository;
use Symfony\Component\HttpFoundation\JsonResponse;

class PostController
{
    protected $repo;

    public function __construct(PostRepository $repo)
    {
        $this->repo = $repo;
    }

    public function indexJson()
    {
        return new JsonResponse($this->repo->findAll());
    }
}
<?php
$app['posts.controller'] = $app->share(function() use ($app) {
    return new PostController($app['posts.repository']);
});

$app->get('/posts.json', "posts.controller:indexJson");

If I'm honest, the jury is still out on this technique for me. The benefits are that my controller code is a little more organised and I can actually practice spec level BDD with my controllers, rather than just functional tests, but sometimes I really just feel like throwing a closure in rather than knocking up a class. Maybe it might work for you, maybe not. Thanks for reading, full code example on github.

php featured silex dependency injection

Maintainable PHP Apps with Silex & BDD

I'm currently writing a book, Maintainable PHP Apps with Silex & BDD, leave me your email address and I'll keep you up to date on my progress.

Twitter Icon If you liked this post, you should follow me on twitter here
blog comments powered by Disqus

About

Photo of Dave Marshall

Dave Marshall has been building web applications with various technologies since around 2004. Dave is a TDD enthusiast, blogs quite regularly at davedevelopment.co.uk and has recently increased his efforts to give back further, by contributing to OS projects such as Silex and Mockery

Read more about Dave

Maintainable PHP Apps with Silex & BDD

I'm currently writing a book, Maintainable PHP Apps with Silex & BDD, leave me your email address and I'll keep you up to date on my progress.

Follow Dave: