Menu
Foto por: Kevin ku

Buenas prácticas con Symfony 4
Controladores, Autowiring, Interfaces

Author image

Escrito por

Álex Martín

Mantener un código limpio y mantenible debería ser el mantra de cualquier desarrollador, pero a veces por las prisas del trabajo diario caemos en introducir malas prácticas, las cuales a futuro perjudican al proyecto y el mantenimiento de éste.

Por esto, deberíamos intentar aplicar ciertas técnicas que nos ayuden a mejorar nuestro código. Este es el primero de una serie de artículos en el que abordaremos el uso de buenas prácticas en nuestro trabajo con Symfony.

Controladores

Los controladores son la pieza que comunican nuestros modelos de datos y las vistas que lo representan, es por ello que es más que probable que añadamos demasiada lógica en éstos.

Por si fuese poco, la propia arquitectura (y documentación) de Symfony nos hace obviar el principio de responsabilidad única (SLR) e introducimos varios propósitos en una misma clase.

Podemos seguir las siguientes buenas prácticas:

  1. Tener un controlador por cada ruta.

  2. El controlador debe llamar a un servicio que será el que incluya la lógica.

  3. Usar el método mágico _invoke para poder llamar al método como si de una función se tratase

Al usar controladores invocables, no necesitaremos especificar el método en nuestro fichero de configuración routes.yaml

app_hello:
	path: /hello
	controller: App\controller\HelloController

Al segregar en un controlador por ruta aumentará de manera significativa el número de ficheros, por ello lo ideal es que agrupemos todos los controladores que gestionan una vista en carpetas.

Con esto tendremos una mejor organización, siendo mucho más fácil localizar lo que necesitamos.

Controller
└── Article
    ├── ArticleDetailController.php
    └── ArticleListController.php

De forma análoga, al tener una ruta por controlador, tendremos test con los sets de pruebas de cada uno de éstos por lo que serán test mucho más livianos y mantenibles que sets de test controladores que gestionen muchas rutas.

Recuerda que la lógica de nuestro controladores debe quedar fuera en éste. Para ello usa clases externas que puedes inyectar mediante autowiring.

Autowiring

Los servicios en Symfony 4 nos dan una serie de ventajas de rendimiento y facilidad de uso como no que una clase no se cargada hasta ser usada.

Symfony 4 está altamente orientado a servicios pudiendo acceder a casi cualquier clase de Symfony usando un servicio.

En versiones anteriores, debíamos definir el servicio, pasarle los argumentos e inyectarlos en nuestras clases.

<?php
namespace App\Services;
use Doctrine\ORM\EntityManager;

class MyService {

    private $em;
    public function __construct (EntityManager $em) {
        $this->em = $em;
    }
app.myService:
        class: App\Services\MyService
        arguments: ["@doctrine.orm.entity_manager"]

Cuando teníamos muchos argumentos, la tarea de crear servicios podría llegar a ser muy tediosa. A partir de la versión 2.8 es cuando llega autowiring para simplificar nuestra tarea con las dependencias de nuestros servicios.

app.myService:
        class: App\Services\MyService
        autowire: true

Una vez el servicio está declarado como autowire, todas las dependencias establecidas en el constructor de nuestras clases serán inyectadas automáticamente basado en su type-hinting.

A tener en cuenta que si vamos a inyectar parámetros personalizados en nuestros servicios, podemos hacerlo desde una configuración explícita

App\Services\MyService:
       arguments:
           $adminEmail: 'manager@example.com'

Interfaces

Una buena práctica que nos ayuda a implementar el Principio de inversión de dependencias es que hagamos que las dependencias de nuestras clases estén programadas contra interfaces y no contra implementaciones. De esta manera desarrollamos en base a contratos y hacemos que nuestro código sea más mantenible y extensible al poder sustituir las piezas unas por otras siempre y cuando este contrato se cumpla.

Debemos tener en cuenta que una vez que usemos interfaces, el sistema de Autowiring de Symfony no sabrá qué clase debe implementar. Por ello debemos crear en nuestra configuración un alias que nos asegure su correcto funcionamiento

services:
    # ...
    App\Services\MyService: ~
    App\Services\MyServiceInterface: '@App\Services\MyService

Desde la versión 3.4, Symfony podemos acceder a muchos de los servicios del framework mediante interfaces.