Symfony vs Laravel: aplicaciones sólidas

symfony

Por qué no puede tener una aplicación sólida usando Laravel

Estamos comparando cómo se maneja el ORM en Laravel vs Symfony.


EL PROBLEMA:

El hecho de que los modelos de laravel estén estrechamente acoplados con la base de datos y eso viola el principio de inversión de dependencia.

 

Aquí hay un ejemplo, supongamos que en un proyecto de Laravel tenemos un modelo llamado Publicación que representa la tabla de publicaciones. ahora imagine un PostController que solo tiene un método show:

<?php

namespace App\Http\Controllers;use App\Models\Post;class PostController extends Controller
{
    public function show(Post $post)
    {
        return view('post.show', ['post' => $post]);
    }
}

¿Se ve bien verdad? Pero espere… ¿qué podría estar mal con esto?

Simplemente ignoremos el hecho de que laravel está consultando la base de datos en el modo de enrutamiento para proporcionarnos $post (middleware SubstituteBindings que se puede desactivar) y supongamos que estamos controlando la conexión a la base de datos. Ahora, si queremos aplicar el principio de inversión de dependencia en SOLID, podemos usar el patrón de repositorio (el repositorio en sí mismo con la inyección de dependencia usando el contenedor de servicios es más o menos el material estándar y no voy a hablar de ellos. También Laravel solía tener un tutorial en su documentación que usó el patrón de repositorio):

<?php

namespace App\Http\Controllers;use App\Models\Post;class PostController extends Controller
{
    public function show($id,PostRepositoryInterface $postRepository)
    {
        $post = $postRepository->find($id);
        return view('post.show', ['post' => $post]);
    }
}

Ahora parece que hemos solucionado la infracción de DI, pero tenemos la variable $post con el tipo de App\Models\Post que hereda el modelo Eloquent, ¡esto significa que todavía tenemos todo tipo de métodos relacionados con la base de datos disponibles en nuestra entidad!

Déjeme elaborar, supongamos que esta es nuestra vista:

<div>{{$post->delete()}}</div>

¡Ahí va nuestra publicación! Este ejemplo puede parecer extraño y algunos se preguntarán por qué alguien haría eso, pero ese no es el punto aquí, sino la inversión de dependencia.

¿Por qué este método debería estar disponible en el modelo de datos en primer lugar?

Esta es la definición de que nuestro modelo de datos está estrechamente acoplado con la capa de la base de datos, lo que viola el principio DI.

Ahora veamos cómo se vería esto en Symfony:

<?php

namespace App\Controller;

use App\Service\PostRepositoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class PostController extends AbstractController
{
    #[Route('/post/{id}')]
    public function show(int $id, PostRepositoryInterface $postRepository): Response
    {
        $post = $postRepository->find($id);
        //$post->delete() would throw undefined exception        return $this->render('post/show.html.twig', [
            'title' => $post->getTitle(),
        ]);
    }
}

En Symfony, el objeto $post es un objeto de modelo de datos puro y no tiene ninguna conexión con la base de datos. No hay ningún método como guardar, eliminar u otro en el modelo de datos en sí.

Básicamente, aún si no usamos el patrón de repositorio en Symfony, adjuntaría nuestro modelo a la doctrina para trabajar con la base de datos, veamos nuestro modelo aquí:

<?php

namespace App\Entity;// note that the orm and the repository is only being used as annotations
use App\Repository\PostRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: PostRepository::class)]
class Post
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column()]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }
}

Como puede ver, nuestro modelo no hereda ninguna otra clase y la información de la base de datos se especifica como anotaciones, que actúan como metadatos y no afectarían al modelo en sí.


Algunos dirían que podemos ignorar solo este principio en los principios SOLID, eso no va a ser tan dramático, ¿verdad?

El problema es que este olor a código no solo viola el principio DI, si consideramos la arquitectura limpia aquí:

symfony

Una de las reglas principales de la arquitectura limpia es que las capas internas no deben acceder a la capa externa y la única comunicación debe ser de las capas externas a las capas internas.

En nuestro ejemplo, la violación que está ocurriendo es que la capa más interna (Entidades) está accediendo a la capa más externa (DB), ¡la peor violación posible!

¿Qué pasa si no creamos en la arquitectura SÓLIDA o Limpia?

Bien, imagine que tiene un proyecto que almacena publicaciones en la base de datos y decide usar algún otro almacén de datos que Laravel no admite de forma predeterminada. Por ejemplo, almacena las publicaciones en Cassandra.

Ahora imagine un escenario en el que muestra una publicación de Cassandra, carga una sola publicación y crea un modelo que representa esa publicación

Ahora realmente no puede usar el modelo Eloquent porque ha usado los métodos relacionados con la base de datos a lo largo de su proyecto que se conecta a la base de datos, luego debe crear un nuevo modelo de datos y refactorizar la mayor parte del código relacionado con ese modelo de datos.

Pero si ha usado el modelo de estilo Symfony, las partes que funcionan con el modelo en sí no necesitarán refactorización porque puede usar el mismo modelo de datos (no un modelo de datos que esté estrechamente acoplado con la base de datos).

No estoy diciendo que al usar Symfony definitivamente no necesitará refactorizar cuando cambie a Cassandra, ya que depende de muchos factores, pero en una circunstancia normal definitivamente necesita menos refactorización o incluso podría ser posible sin refactorización y simplemente agregando el repositorio

Ahora todavía puede usar Laravel sin usar Eloquent, pero no parece una buena solución cuando Symfony se basa en la mejor solución.

Realmente me gustaría leer sus comentarios sobre esto porque no pude encontrar mucho al respecto. Tampoco dudes en atacarme cualquier cosa con la que no esté de acuerdo.


Si le interesa, puede echar un vistazo a algunos de los otros artículos que he escrito recientemente sobre Laravel:

Recent Post