PHP & Symfony About PHP and Symfony2 development

Symfony2: How to create framework independent controllers?

Posted on by Matthias Noback

Part I: Don't use the standard controller

The general belief is that controllers are the most tightly coupled classes in every application. Most of the time based on the request data, they fetch and/or store persistent data from/in some place, then turn the data into HTML, which serves as the response to the client who initially made the request.

So controllers are "all over the place", they glue parts of the application together which normally lie very far from each other. This would make them highly coupled: they depend on many different things, like the Doctrine entity manager, the Twig templating engine, the base controller from the FrameworkBundle, etc.

In this post I demonstrate that this high level of coupling is definitely not necessary. I will show you how you can decrease coupling a lot by taking some simple steps. We will end with a controller that is reusable in different types of applications, e.g. a Silex application, or even a Drupal application.

Unnecessary coupling: extending the base controller

Most Symfony controllers I come across extend the Controller class from the FrameworkBundle:

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class MyController extends Controller
{
    ...
}

The base controller class offers useful shortcuts, like createNotFoundException() and redirect(). The base class also makes your controller ContainerAware, which means the service container will be automatically injected. You can then pull from it any service you need.

Don't use the helper methods

This all sounds really nice, but being just a little less lazy will decrease coupling a lot. It's absolutely no problem to leave those "highly useful" shortcut methods out. They mostly are simple one-liners (take a look at the standard Controller class to see what is really going on). You can simply replace the function calls with their actual code (e.g. make it an "inline method").

class MyController extends Controller
{
    public function someAction()
    {
        throw $this->createNotFoundException($message);

        // becomes

        return new NotFoundHttpException($message);
    }
}

Use dependency injection

If you don't use any of the helper functions from the parent Controller class anymore, you need to take one more step before you can remove the extends Controller part from the class definition: you have to inject the dependencies of your controller manually instead of fetching them from the container:

class MyController extends Controller
{
    public function someAction()
    {
        $this->get('doctrine')->getManager(...)->persist(...);
    }
}

// becomes

use Doctrine\Common\Persistence\ManagerRegistry;

class MyController extends Controller
{
    public function __construct(ManagerRegistry $doctrine)
    {
        $this->doctrine = $doctrine;
    }

    public function someAction()
    {
        $this->doctrine->getManager(...)->persist(...);
    }
}

Turn your controller into a service

This means you also need to make sure that the controller is not instantiated by just using the new operator, like the ControllerResolver does, since that would prevent you from injecting any dependencies at all. Instead, define it as a service:

services:
    my_controller:
        class: MyController
        arguments:
            - @doctrine

Now you need to modify your routing configuration too. If you use annotations:

/**
 * @Route(service="my_controller")
 */
class MyController extends Controller
{
    ...
}

If you use a configuration file:

my_action:
    path: /some/path
    defaults:
        _controller: my_controller:someAction

Finally, there is no need to extend from the standard Framework controller: we don't need it to be container-aware since we inject everything that's needed using constructor arguments. And we also don't use any of the helper functions offered by the standard controller class, so we can truly remove the extends Controller part of the class definition, including the use statement we introduced for it:

class MyController
{
}

This earns us a lot of decoupling points!

In the next post, we will discuss the use annotations and how getting rid of them will decrease coupling even further.

Categories: PHP Symfony

Tags: controller reusability coupling

Comments: Comments