How to make framework-independent controllers?

Original author: Matthias Noback
  • Transfer
  • Tutorial
Controllers are generally considered to be the most related classes in an application. As a rule, based on the request data, they receive or save the data in a database, then turn the data or the result of the storage into HTML, which acts as a response to the client who made the request.

It turns out that controllers are everywhere, they connect those parts of the application that are usually quite independent of each other. This greatly enhances the connectedness of controllers: among their dependencies are the Doctrine entity manager, Twig template engine, the base controller from FrameworkBundle, and so on.

In this post, I will show that this level of connectivity is completely unnecessary. I will show you how to significantly reduce connectivity by taking just a few simple steps. As a result, we get a controller that can be reused in different types of applications, for example, based on Silex or even Drupal.

Part I: Do Not Use Standard Controllers



Excessive connectivity: inheritance from the base controller

Basically, the controller classes that I come across inherit from the Controller class from FrameworkBundle:

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
    ...
}


The base class of the controller adds several useful abbreviations, such as createNotFoundException () and redirect (). It also automatically makes your controller the successor of ContainerAware (container aware), as a result of which the Dependency Injection Container (DIC) will automatically be injected into the controller. From the container you can get any service you need.

Do not use helper methods!

All this, of course, seems very convenient, but if you are a little less lazy, you can significantly reduce the connectedness. There is nothing wrong with throwing out these auxiliary methods: the bodies of most of them all the same consist of only one line (look at the base controller to understand what is really happening there!). You can easily replace the calls of these methods with the code from them:

class MyController extends Controller
{
    public function someAction()
    {
        // было:
        throw $this->createNotFoundException($message);
        // стало:
        throw new NotFoundHttpException($message);
    }
}


Use Dependency Injection

If you no longer use helper methods from the controller’s parent class, then you can take another step to get rid of this actually unnecessary inheritance. Instead of getting dependencies from the container, inject them manually in the constructor:

class MyController extends Controller
{
    public function someAction()
    {
        $this->get('doctrine')->getManager(...)->persist(...);
    }
}
// превратится в:
use Doctrine\Common\Persistence\ManagerRegistry;
class MyController extends Controller
{
    public function __construct(ManagerRegistry $doctrine)
    {
        $this->doctrine = $doctrine;
    }
    public function someAction()
    {
        $this->doctrine->getManager(...)->persist(...);
    }
}


Make your controller a service

You also need to make sure that the new controller service is not collected simply by calling the new operator, as ControllerResolver usually does, and all the dependencies of your controller are correctly injected. To do this, you need to declare a service:

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

And the routing settings also need to be fixed. If you use annotations:

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

And if your routing is saved in the YML configuration file:

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


Finally, there is no longer any need to inherit from the standard symphony controller, we also do not need the controller to know anything about the DI container, since all its dependencies are injected into the constructor arguments. And we are no longer dependent on auxiliary methods of the base class, which means that now we can finally remove the extends Controller along with the use expression from the declaration of our controller's class:

class MyController
{
}


That's how we get a lot of bonus points for an unrelated code!

In the next post, we'll talk about annotations and how our code will become even less connected if we get rid of them.

Also popular now: