Alejarse de la magia - o: por qué ya no quiero usar Laravel

Es hora de un cambio en las herramientas que utilizo. ¡Y te diré por qué!

En primer lugar, quiero asegurarme de que conozca mis intenciones. No estoy tratando de despotricar sobre Laravel o por qué otros marcos podrían ser mejores.

Este artículo es muy subjetivo. Le daré mis pensamientos y trataré de que también reconsidere sus opciones de marco. Y cuando te quedas con Laravel después de reevaluar, está bien. No tengo ninguna intención de convertir a la gente de Laravel a otros marcos o lenguajes. Pero es importante mirar más de cerca y asegurarse de saber qué está usando y por qué lo está usando.

Intro

He trabajado con Laravel durante aproximadamente 2 años. Siempre me gustó lo fácil que era poner en marcha una aplicación y empezar en minutos. Proporciona tantas herramientas útiles listas para usar. Los comandos de la consola lo ayudan en todos los aspectos durante la codificación. Generan clases, scaffolding para autenticación y mucho más.

Pero cuanto más profundice y más grandes se vuelvan los proyectos, más complicado se volverá el desarrollo con Laravel. O permítanme reformularlo: las otras herramientas mejores harán el trabajo. Ni siquiera digo que sea solo culpa de Laravel. También se debe en parte a que PHP no está muy bien diseñado.

¡Ahora comencemos!

ORM elocuente

Si ya has trabajado con Laravel, seguro que conoces Eloquent. Es el ORM que se envía con una instalación predeterminada. Viene con muchas características interesantes. Pero su diseño hace que su aplicación sea innecesariamente compleja y evita que el IDE analice correctamente su código.

Esto se debe en parte al patrón ORM de Active Record que se está utilizando y en parte al hecho de que Eloquent quiere evitar que el desarrollador tenga que escribir más código. Para hacer eso, le permite al desarrollador introducir muchas cosas en el modelo que no pertenece allí.

Suena a buenas intenciones, pero esto me empezó a disgustar cada vez más.

Echemos un vistazo a un ejemplo:

Lo primero que ve es que no hay propiedades en el modelo. Esto parece irrelevante, pero para mí, marca una gran diferencia. Todo se inyecta "mágicamente" en la clase leyendo los metadatos de la tabla. Por supuesto, su IDE no entiende eso sin ayuda. Y no tiene la oportunidad de nombrar sus propiedades de manera diferente a sus columnas.

Ahora mira el método de alcance. Para los usuarios de Laravel, está bastante claro lo que hace. Si llama a este método, determina el alcance de la consulta SQL subyacente agregando la cláusula WHERE proporcionada.

Como puede ver, no es estático. Eso significaría que este método opera sobre un objeto específico de la clase. Pero en este caso no es así . Se llama a un ámbito en un generador de consultas. No tiene nada que ver con el objeto modelo en sí. Lo explicaré después de que vea cómo suele llamar a esos ámbitos:

Estás llamando a un método estático popular()que nadie definió nunca. Pero como Laravel define un método __call()y __callStatic(), se maneja a través de ellos. Estos métodos reenvían la llamada a un generador de consultas.

Esto no es solo algo que su IDE no comprende. Hace que la refactorización sea más difícil, puede confundir a los nuevos desarrolladores y el análisis estático también se vuelve más difícil.

Además, al poner tales métodos en su modelo, está violando la S de SOLID. En caso de que no esté familiarizado con eso, SOLID es un acrónimo que significa:

  • S Responsabilidad ingle Principio
  • O pen / Cerrado Principio
  • Principio de sustitución de L iskov
  • Me nterface Segregación Principio
  • D ependency Inversión Principio

Cuando usa Eloquent, sus modelos tienen múltiples responsabilidades. Contiene los datos de su base de datos, que es lo que suelen hacer los modelos, pero también tiene lógica de filtrado, tal vez clasificación e incluso más. No quieres eso.

Ayudantes globales

Laravel viene con bastantes funciones de ayuda globales. Parecen muy prácticos y sí, son prácticos.

Solo tiene que saber que sacrifica su independencia por esa practicidad y su espacio de nombres global se contamina. Rara vez conduce a conflictos, pero debe preferirse evitarlos por completo.

Veamos algunos ejemplos. Aquí hay una lista de tres métodos auxiliares que tenemos pero que no necesitamos ya que existen mejores alternativas:

  • app_path()- ¿por qué? Si necesita la ruta de la aplicación, pregunte al objeto de la aplicación. Lo obtienes por sugerencia de tipo.
  • app()- ¿eh? No necesitamos este método. ¡Podemos inyectar la instancia de la aplicación!
  • collect()- Esto crea una nueva instancia de la clase Collection. Podemos simplemente crear un objeto por nosotros mismos.

Un ejemplo más concreto:

Estamos utilizando el request()ayudante global de Laravel para recuperar los datos POST y ponerlos en nuestro modelo como atributos.

En lugar de utilizar el ayudante global, podríamos escribir hint un Requestobjeto como parámetro en el método del controlador. El despachador en Laravel sabe cómo proporcionarnos el objeto necesario. Llamará a nuestro método con él y no tenemos que llamar a un ayudante.

Y podemos llevar esto un paso más allá para desacoplarnos aún más. Laravel es compatible con PSR-7. Por lo tanto, en lugar de escribir insinuando el objeto Solicitud, también podría escribir sugerencia ServerRequestInterface. Eso le permite reemplazar todo el marco con cualquier cosa que sea compatible con PSR-7. Todo en este método seguirá funcionando. Esto fallaría si todavía usa los métodos auxiliares. El nuevo marco no vendría con el método auxiliar y, por lo tanto, tendría que volver a escribir esa parte de su código.

Rara vez cambia todo el marco, pero hay personas que lo hacen. E incluso si es posible que nunca cambie, sigue siendo importante para la interoperabilidad. Ser capaz de inyectar dependencias y tener un flujo de datos conciso en lugar de resolver y solicitar dependencias y datos de adentro hacia afuera es el camino a seguir. Hace que las pruebas, la refactorización y casi todo sea más fácil cuando lo dominas.

Me alegré cuando leí que con Laravel 5.8 los ayudantes de cadena y matriz se eliminan del núcleo y se colocan en un paquete separado. Este es un buen primer paso. Pero la documentación debería empezar a desalentar el uso de todas las funciones auxiliares.

Fachadas

Aquí también entran en juego los argumentos de la última parte. Las fachadas parecen ser una buena herramienta para acceder rápidamente a algunos métodos que no son realmente estáticos. Pero te atan al marco una vez más. Los usa para resolver dependencias manualmente en lugar de indicarle al entorno que las proporcione.

Lo mismo ocurre con la complejidad al pasar todo por los métodos mágicos.

Ya que estábamos hablando sobre el soporte de IDE, sé que algunos de ustedes podrían dirigirme al paquete de ayuda de IDE de barryvdh. No es necesario. Ya conozco este paquete. Pero, ¿por qué es necesario? Porque algunas decisiones de diseño en Laravel simplemente no son buenas. Hay marcos en los que no es necesario. Tome Symfony por ejemplo. No se necesitan archivos de ayuda IDE, porque está bien diseñado e implementado.

En lugar de fachadas, podríamos usar la inyección de dependencia nuevamente como hicimos en el ejemplo anterior. Tendríamos un objeto real y podríamos llamar a métodos reales sobre él. Mucho mejor.

Una vez más les daré un ejemplo:

Podríamos limpiar esto fácilmente. Digamos a Laravel que inyecte ay ResponseFactorynos pase la solicitud actual:

Ahora hemos eliminado con éxito el uso de fachadas de nuestro controlador. El código todavía se ve limpio y compacto, si no mejor que antes. Y dado que nuestros controladores siempre amplían la Controllerclase general , podríamos llevar todo un paso más allá moviendo la fábrica de respuestas a esa clase principal. De todos modos, lo necesitamos en todas las demás clases de controladores.

Escuché que algunas personas proporcionan "demasiados parámetros de constructor" como argumento en contra de inyectar todo. Pero no estoy de acuerdo con eso. En primer lugar, solo oculta las dependencias y, por lo tanto, la complejidad. Si no le gusta tener de 10 a 20 argumentos en su constructor, tiene razón.

Sin embargo, la solución no es mágica. Necesitar tantas dependencias en una sola clase significa que esta clase probablemente tenga demasiadas responsabilidades. En lugar de ocultar esa complejidad, refactorice esa clase. Divídalo en otros nuevos y mejore la arquitectura de su aplicación.

Dato curioso: hay un patrón de diseño real llamado "patrón de fachada", introducido en el libro de Gang of Four. Pero tiene un significado completamente diferente. Las fachadas de Laravel son esencialmente localizadores de servicios estáticos . El nombre simplemente no transmite eso. El mismo nombre para diferentes cosas también dificulta las discusiones sobre arquitectura en proyectos, porque la otra parte puede esperar algo completamente diferente detrás de ese nombre.

Conclusión

Lleguemos a su fin. Podría escribir un seguimiento pronto sobre qué tecnologías prefiero usar hoy en día. Pero por el momento, permítanme resumir lo que hemos aprendido:

El enfoque de Laravel para hacer que todo sea lo más fácil posible es bueno. Pero es difícil llevarse bien cuando sus aplicaciones se vuelven más avanzadas. Prefiero un excelente soporte IDE, escritura más fuerte, objetos reales y buena ingeniería. Incluso podría volver a Laravel cuando quiera escribir una aplicación más pequeña.

Muchos de mis puntos no son solo culpa de Laravel. Podría cambiar las partes que no me gustan, por ejemplo, el ORM. Pero en cambio, simplemente cambiaré el conjunto de herramientas, donde los valores predeterminados se adaptan mejor a mis necesidades. No veo ningún sentido en usar un marco en el que tengo que dedicar más tiempo a evitar las trampas que crea para la mala ingeniería que a desarrollar mi aplicación. Otros marcos y herramientas vienen con valores predeterminados mejor diseñados y menos magia.

Entonces, por ahora, me despediré de Laravel.

Gracias por tu tiempo. Agradecería una buena discusión en los comentarios y, por supuesto, estoy abierto a sus preguntas y sugerencias.

PD: ¡Un agradecimiento especial a Marco Pivetta por la lectura de pruebas y aportes adicionales!

Editar el 1 de marzo de 2019:

Desde que mi artículo fue publicado en Reddit, he creado una cuenta de Reddit para responder algunos comentarios. Mi cuenta no es desde la que se publicó el artículo, sino esta: //reddit.com/u/nschoellhorn

Editar 13 de marzo de 2019:

Si ha leído hasta aquí, también puede consultar mi perfil de Twitter. ¡Gracias por su continuo interés en este tema! Siempre estoy abierto a discusiones productivas, así que no dude en ponerse en contacto, ya sea aquí o en Twitter.