
Get rid of annotations in your controllers!
- Transfer
- Tutorial
In the previous part of this series, we reduced the connectivity of the symphony controller and the framework, removing the dependency on the base class of the controller from
Now let's look at the annotations. Initially, they were connected to speed up development (the need to edit the configuration file disappears, just solve problems right on the spot!):
When you include these annotations, it
Excellent! And just a few lines of code. But all these automagic things imperceptibly connect our controller with a framework. Although there are no explicit dependencies, there are several implicit dependencies. The controller will only work if connected
1. It (SensioFrameworkExtraBundle) generates routing based on annotations
2. It takes care of turning the returned array into the correct object
3. He guesses which template should be applied
4. He turns a parameter
It would seem that this is not so scary, but
Instead of annotations, we will use the usual configuration files and PHP code.
First of all, make sure that our routes are connected to
You can use YAML, but lately something has hooked on XML.
Make sure that the service
Now you can remove the annotations
Now, instead of hoping for annotation
In the service declaration for this controller, you must also specify the service
After these changes, you can safely remove the annotation
And one more step to lower the connectivity of our controller. We are still dependent on
The service declaration should return the repository we need. We will achieve this in this way:
Finally, we got rid of annotations, which means that our controller can be used outside the Symfony 2 application (that is, one that does not depend on
- HttpFoundation component (for classes
- template (for
- an implementation of repositories Doctrine (Doctrine the ORM, Doctrine MongoDB ODM are, ...)
- the Twig templating for
left there is only one weak point: the names of our templates are still based on framework conventions (i.e. they use the bundle name as a namespace, e.g.
FrameworkBundle
. And in this part, we will get rid of some implicit dependencies that appear due to annotations. Now let's look at the annotations. Initially, they were connected to speed up development (the need to edit the configuration file disappears, just solve problems right on the spot!):
namespace Matthias\ClientBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* @Route("/client")
*/
class ClientController
{
/**
* @Route('/{id}')
* @Method("GET")
* @ParamConverter(name="client")
* @Template
*/
public function detailsAction(Client $client)
{
return array(
'client' => $client
);
}
}
When you include these annotations, it
detailsAction
will be executed when the URL matches the pattern /client/{id}
. The parameter converter will receive the client entity from the database based on the parameter id
that will be extracted from the URL by the router. And the annotation @Template
will indicate that the returned array is a set of variables for the template Resources/views/Client/Details.html.twig
. Excellent! And just a few lines of code. But all these automagic things imperceptibly connect our controller with a framework. Although there are no explicit dependencies, there are several implicit dependencies. The controller will only work if connected
SensioFrameworkExtraBundle
for the following reasons: 1. It (SensioFrameworkExtraBundle) generates routing based on annotations
2. It takes care of turning the returned array into the correct object
Response
3. He guesses which template should be applied
4. He turns a parameter
id
from a query into a real model It would seem that this is not so scary, but
SensioFrameworkExtraBundle
a bundle, which means that it works only in the context of the Symfony 2 application. But we don’t we want to be tied to a specific framework (this is, in fact, the essence of this series of posts), so we need to get rid of this dependency. Instead of annotations, we will use the usual configuration files and PHP code.
We use a router configuration
First of all, make sure that our routes are connected to
Resources/config/routing.xml
:client_controller:detailsAction
You can use YAML, but lately something has hooked on XML.
Make sure that the service
client_controller
actually exists, and do not forget to import the new one routing.xml
in the application settings, in the file app/config/routing.yml
:MatthiasClientBundle:
resource: @MatthiasClientBundle/Resources/config/routing.xml
Now you can remove the annotations
@Route
and @Method
from the controller class!Create your own Response object yourself
Now, instead of hoping for annotation
@Template
, you can very well render the template yourself, and create a Response object containing the rendering result. You just need to inject the template engine into your controller, and specify the name of the template that you want to render:use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
class ClientController
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
/**
* @ParamConverter(name="client")
*/
public function detailsAction(Client $client)
{
return new Response(
$this->templating->render(
'@MatthiasClientBundle/Resources/views/Client/Details.html.twig',
array(
'client' => $client
)
)
);
}
}
In the service declaration for this controller, you must also specify the service
@templating
as an argument to the constructor:services:
client_controller:
class: Matthias\ClientBundle\Controller\ClientController
arguments:
- @templating
After these changes, you can safely remove the annotation
@Template
Get the required data yourself
And one more step to lower the connectivity of our controller. We are still dependent on
SensioFrameworkExtraBundle
, it automatically turns the parameter id
from the request into real entities. This should not be difficult to fix, because we can just get the entity ourselves, using the entity repository directly:...
use Doctrine\Common\Persistence\ObjectRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ClientController
{
private $clientRepository;
...
public function __construct(ObjectRepository $clientRepository, ...)
{
$this->clientRepository = $clientRepository;
...
}
public function detailsAction(Request $request)
{
$client = $this->clientRepository->find($request->attributes->get('id'));
if (!($client instanceof Client) {
throw new NotFoundHttpException();
}
return new Response(...);
}
}
The service declaration should return the repository we need. We will achieve this in this way:
services:
client_controller:
class: Matthias\ClientBundle\Controller\ClientController
arguments:
- @templating
- @client_repository
client_repository:
class: Doctrine\Common\Persistence\ObjectRepository
factory_service: doctrine
factory_method: getRepository
public: false
arguments:
- "Matthias\ClientBundle\Entity\Client"
Finally, we got rid of annotations, which means that our controller can be used outside the Symfony 2 application (that is, one that does not depend on
FrameworkBundle
or on SensioFrameworkExtraBundle
). All dependencies explicit, ie the controller to work, you need: - HttpFoundation component (for classes
Response
and NotFoundHttpException
) - template (for
EngineInterface
) - an implementation of repositories Doctrine (Doctrine the ORM, Doctrine MongoDB ODM are, ...)
- the Twig templating for
left there is only one weak point: the names of our templates are still based on framework conventions (i.e. they use the bundle name as a namespace, e.g.
@MatthiasClientBundle/...
) This is an implicit framework dependency, as these namespaces are registered in the bootloader from the Twig file system. In the next post we will deal with this problem too.