Skip to content

Instantly share code, notes, and snippets.

@davedevelopment
Last active December 10, 2015 14:38
Show Gist options
  • Select an option

  • Save davedevelopment/4449152 to your computer and use it in GitHub Desktop.

Select an option

Save davedevelopment/4449152 to your computer and use it in GitHub Desktop.
Controllers as a Service as a Decorator

How to define Controllers as Services

As your Silex application grows, you may wish to begin organizing your controllers in a more formal fashion. Silex can use controller classes out of the box, but with a bit of work, your controllers can be created as services, giving you the full power of dependency injection and lazy loading.

Example Application

Here's a make believe blog application, we're going to change the /posts.json route to use a controller class, that is defined as a service:

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());
});

Controller Resolver

By default, Silex uses Symfony's ControllerResolver to help convert whatever was defined as the controller for the current route in to something it can invoke. We want to override the default implementation, to allow for a format taken from the full stack framework, whereby two strings separated by a single colon, represent a service ID and the method to call on that service. For example, posts.controller:indexJsonAction should resolve to the indexJsonAction method on the posts.controller service. Add the following class under your app's namespace:

namespace Demo\Controller;

use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;

class ServiceControllerResolver implements ControllerResolverInterface
{
    const SERVICE_PATTERN = "/[A-Za-z0-9\._\-]+:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/";

    protected $resolver;
    protected $app;

    public function __construct(ControllerResolverInterface $resolver, Application $app)
    {
        $this->resolver = $resolver;
        $this->app = $app;
    }

    public function getController(Request $request)
    {
        $controller = $request->attributes->get('_controller', null);

        if (!is_string($controller) || !preg_match(static::SERVICE_PATTERN, $controller)) {
            return $this->resolver->getController($request);
        }

        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);
    }

    public function getArguments(Request $request, $controller)
    {
        return $this->resolver->getArguments($request, $controller);
    }
}

We then simply extend Silex's built in resolver service with the decorator:

$app['resolver'] = $app->extend('resolver', $app->share(function ($resolver) use ($app) {
    return new Demo\Controller\ServiceControllerResolver($resolver, $app);
}));

Controller Implementation

Writing your controller is pretty simple, create a Plain Ol' PHP Object (POPO) with your PostRepository as a dependency, along with a indexJsonAction method to handle the request. Although not shown in the example below, you can use type hinting and parameter naming to get the parameters you need, just like with standard Silex routes.

If you are a TDD/BDD fan (and you should be), you may notice that this controller has a well defined responsibility and is easily tested/specced:

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 indexJsonAction()
    {
        return new JsonResponse($this->repo->findAll());
    }
}

And lastly, define your controller as a service in the application:

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

$app->get('/posts.json', "posts.controller:indexJsonAction");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment