PHP y principios SOLID
Los principios SOLID explicados con ejemplo de código escrito en PHP
SOLID es un acrónimo para simbolizar los 5 principios básicos de diseño a la hora de trabajar con el paradigma de la programación orienta a objetos. Ajustarnos a estos principios nos permitirá desarrollar arquitecturas mucho más fáciles de mantener y extensibles por lo que conviene familiarizarse con ellos de cara a entender sus implicaciones y saberlas aplicar.
En este artículo quiero explicar los principios SOLID a partir de ejemplos escritos en PHP de cara a que todos aquellos que programéis con Symfony u otros frameworks de alto nivel os familiaricéis con ellos.
¡Vamos a verlos!
Dame una S: Single-responsibility principle
Una clase debería tener una única razón para cambiar
Dicho de otro modo, una clase debería realizar una única tarea ya que en el momento en que es responsable de varias comienza a haber acoplamiento de funcionalidad (lo cual se traduce en un código poco resiliente a los cambios).
Por ejemplo, la siguiente clase User
está infringiendo el principio de responsabilidad única:

ya que el método insertInDB
está fuera del alcance de la misma. Por tanto, la solución correcta sería tener dos clases para evitar ese tipo de acoplamiento:


Dame una “O”: Open-Closed principle
Los objetos deberían estar abiertos para su extensión y cerrados para su modificación
De acuerdo a este principio, una entidad debería ser posible extenderla con nuevas funcionalidades sin necesidad de modificar su código.
Supongamos por ejemplo que tenemos que calcular la suma total de las áreas de una serie de figuras geométricas. Una posible implementación que no repetiría este principio sería la siguiente:

¿Por qué? Porque cada vez que añadamos un nuevo tipo de figura nos veremos obligados a modificar el método sum
de la clase AreaCalculator
de cara a añadir el nuevo tipo de figura.
Para resolver este problema una solución sería crear la interfaz Shape
que declararía el método area
que todas las figuras deberían implementar para devolver su área.
De este modo, el método sum
de AreaCalculator
tan sólo tendría que llamar al método area
de cada objeto (pues todos implementan la interfaz Shape
) sin preocuparse del tipo de objeto que sea.

Dame una “L”: Liskov Substitution principle
Cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas.
Aunque formalmente se define como:
Sea ϕ(x) una propiedad comprobable acerca de los objetos x de tipo T. Entonces ϕ(y) debe ser verdad para los objetos y del tipo S donde S, es un subtipo de T.
Imaginad por ejemplo que tenemos dos tipos de vehículos: Car
y Bike.
Según las circunstancias podríamos emplear cualquiera de ellos siendo la única diferencia la velocidad a la que se mueven. El principio de Liskov nos obliga a que nuestra aplicación funcione sin necesidad de comprobar la clase a la que pertenecen los objetos.
Por ejemplo, un código que no cumpliría este principio sería el siguiente:

Ya que en la línea 19 estamos comprobando el tipo de vehículo y nuestra aplicación debería ser capaz de funcionar sin esa comprobación.
Un ejemplo de código que cumple este principio podría ser:

Dame una “I”: Interface Segregation principle
Un cliente nunca debería verse obligado a implementar una interfaz que no utiliza y debería verse obligado a depender de métodos que no utiliza.
Es decir, una clase no debería implementar una interfaz que no vaya a usar ni las interfaces deberían estar sobrecargadas de métodos que no todas las clases que las implementen los necesitarán.
Por tanto, la solución es plantear interfaces específicas en vez de interfaces generales que cubran excesivos casos de uso.
Por ejemplo, si tenemos máquinas capaces de hacer café y té, una posible implementación que no respetase este principio sería:

Esto se debe a que la interfaz BeverageMachine
es demasiado general de modo que las clases que la implementan se ven obligadas a lanzar excepciones en los métodos que no necesitan.
Una implementación correcta de acuerdo a este principio sería:

Dame una “D”: Dependency Inversion principle
A. Las clases de alto nivel no deberían depender de las clases de bajo nivel. Ambas deberían depender de las abstracciones.
B. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.
Es decir, una clase particular no debería depender directamente de una clase sino de la abstracción de la misma de modo que se favorezca la reusabilidad frente al acoplamiento.
Por ejemplo, una clase que requiera una conexión a una base de datos no seguiría este principio si es declarada así:

Pues estaría acoplada al tipo de base de datos que emplee la aplicación por lo que si cambiáramos de sistema nos veríamos obligados a modificarla (esta implementación también rompe con el principio “Open-Closed”).
La solución correcta sería declarar una interfaz DBConnectionInterface
sobre la que trabajaría UserDB
:

De este modo, si cambiamos de tipo de base de datos SomeClass
seguirá funcionando pues depende de la abstracción proporcionada por DatabaseConnectionInterface
.
SOLID
Como veis, estos principios representan el punto de partida para desarrollar una arquitectura que permita ser ampliada, reutilizada y refactorizada de un modo mucho más sencillo, por lo que mi consejo es que os familiaricéis con ellos y busquéis los lugares de vuestras aplicaciones donde seguirlos.
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇