Una introducción a los patrones de diseño en PHP (y su aprovechamiento en Drupal)

Publicado: 2022-07-05

Alguien inteligente dijo una vez: buen código de codificador, excelente reutilización.
Los desarrolladores a menudo se encuentran resolviendo el mismo tipo de problemas repetidamente. La reutilización de código es verdaderamente el santo grial del desarrollo de software. Además, ¿a quién no le gusta leer (y escribir) código bien estructurado? Enter - patrones de diseño en PHP.

Los patrones de diseño de PHP han demostrado ser extremadamente útiles para los desarrolladores y son un gran solucionador de problemas. Seguir las mejores prácticas es crucial para escribir código eficiente. Los patrones de diseño de PHP son un concepto de programación orientada a objetos (POO) que ahora también se usa en los proyectos de Drupal 9. Con la adopción de conceptos modernos de PHP y programación orientada a objetos por parte de Drupal desde la versión 8, los patrones de diseño se pueden aprovechar para una programación más limpia y sólida. En este artículo, discutiremos algunos patrones de diseño de uso común en PHP y cómo usar patrones como inyecciones de dependencia en Drupal.

¿Sigues en Drupal 7? Lea este artículo para encontrar una lista de verificación útil que lo ayudará a prepararse para una migración a Drupal 9.

Patrones de diseño en PHP

¿Qué son los patrones de diseño en PHP?

En ingeniería de software, un patrón de diseño es una solución general repetible para un problema común en el diseño de software. Los buenos diseños orientados a objetos deben ser reutilizables, mantenibles y extensibles , y los patrones de diseño en PHP pueden ser muy útiles para hacerlo. No solo ayuda en la resolución de problemas, sino que implica una forma óptima de abordar los desafíos comunes.

¿Por qué usar patrones de diseño de PHP?

Algunos de los beneficios más significativos de implementar patrones de diseño en PHP son:

  • Los patrones de diseño de PHP ayudan a resolver los problemas repetitivos que se enfrentan durante el desarrollo
  • El uso de patrones de diseño en PHP hace que la comunicación entre diseñadores y desarrolladores sea más eficiente
  • Puede estar seguro de que otros desarrolladores entenderán su código, ya que sigue patrones de diseño.
  • Seguir las mejores prácticas ayuda a crear aplicaciones más sólidas
  • Ayuda a que el desarrollo sea más rápido y fácil.

Patrones de diseño ampliamente utilizados en PHP

Los patrones de diseño se pueden utilizar en diversas situaciones para resolver problemas similares. Hay más de 30 patrones de diseño que se pueden clasificar en términos generales en tres tipos: patrones de creación, estructurales y de comportamiento.

Patrones de creación: patrones de diseño que se utilizan en los mecanismos de creación de objetos, para crear objetos que se pueden desacoplar del sistema que los implementó.

Patrones estructurales: esto facilita el diseño al identificar formas simples de realizar relaciones entre entidades

Patrones de Comportamiento: Se utilizan para gestionar relaciones, responsabilidades y algoritmos entre objetos.

Patrón de fábrica

Un patrón de fábrica se utiliza para construir un objeto. Así es, construye un objeto y no crea un objeto. Cuando construimos el objeto, primero lo creamos y luego lo inicializamos. Por lo general, requiere aplicar cierta lógica y realizar varios pasos. Con eso, tiene sentido tener todo eso en un solo lugar y reutilizarlo cada vez que necesite tener un nuevo objeto construido de la misma manera. Fundamentalmente, ese es el uso del patrón de fábrica.
Es una gran idea tener una interfaz para nuestra fábrica y que nuestro código dependa de ella y no de una fábrica concreta.

 interface FamilyFactoryInterface { public function create() : Family }

A continuación, implemente la interfaz de fábrica con la siguiente clase:

 class FamilyFactory implements FamilyFactoryInterface { public function create() : Family { $family = new Family(); // initialize your family return $family; } }

Patrón de adaptador

En el patrón de diseño de adaptador, una clase transforma la interfaz de una clase en otra clase. En este ejemplo, tenemos una clase TextBook que tiene métodos getTitle() y getAuthor(). El cliente espera un método getTitleAndAuthor(). Para "adaptar" SimpleBook para demoAdapter , tenemos una clase adaptadora, BookAdapter , que toma una instancia de TextBook y usa los métodos TextBook getTitle() y getAuthor() en su propio método getTitleAndAuthor.

 <?php class TextBook { private $title; private $author; function __construct($title_in, $author_in) { $this->title = $title_in; $this->author = $author_in; } function getTitle() { return $this->title; } function getAuthor() { return $this->author; } } class BookAdapter { private $book; function __construct(TextBook $book_in) { $this->book = $book_in; } function getTitleAndAuthors() { return $this->book->getTitle().' by '.$this->book->getAuthor(); } } // client writeln('BEGIN TESTING ADAPTER PATTERN'); writeln(''); $book = new TextBook("Gamma, Helm, Johnson, and Vlissides", "Design Patterns"); $bookAdapter = new BookAdapter($book); writeln('Author and Title: '.$bookAdapter->getTitleAndAuthor()); writeln(''); writeln('END TESTING ADAPTER PATTERN'); function writeln($line_in) { echo $line_in."<br/>"; } ?>

Patrón PHP Singleton

Para limitar la creación de instancias de una clase a un solo objeto, se utiliza un patrón singleton en PHP. Esto puede ser útil cuando solo se necesita un objeto en todo el sistema. Tiene sentido permitir el acceso a solo una instancia de una determinada clase al diseñar aplicaciones web. Para evitar la creación explícita de objetos de la clase de patrón Singleton, se utiliza un constructor privado.

 <?php class Singleton { public static function getInstance() { static $instance = null; if (null === $instance) { $instance = new static(); } return $instance; } protected function __construct() { } private function __clone() { } private function __wakeup() { } } class SingletonChild extends Singleton { } $obj = Singleton::getInstance(); var_dump($obj === Singleton::getInstance()); $obj2 = SingletonChild::getInstance(); var_dump($obj2 === Singleton::getInstance()); var_dump($obj2 === SingletonChild::getInstance()); ?>

Patrón observador en PHP

El patrón PHP Observer se usa para alertar al resto del sistema sobre eventos particulares en ciertos lugares.
Por ejemplo, si necesitamos crear un Teatro para mostrar películas a los críticos. Definimos la clase Teatro con el método actual. Antes de presentar la película, queremos enviar mensajes a los celulares de los críticos. Luego, en medio de la película, queremos detener la película durante 5 minutos para que los críticos tengan un intervalo. Finalmente, tras el final de la película queremos pedir a los críticos que dejen su respuesta. Entonces, en el patrón de observador para PHP, el objeto observador solo recibe una notificación cuando se cambia el estado.

Así es como se ve el código:

 class Theater { public function current(Movie $movie) : void { $critics = $movie->getCritics(); $this->message->send($critics, '...'); $movie->play(); $movie->pause(5); $this->progress->interval($critics) $movie->end(); $this->response->request($critics); } }

Patrón Decorador para PHP

El patrón Decorator se utiliza cuando desea modificar el carácter de un objeto en tiempo de ejecución y, con ello, reducir las herencias innecesarias y el número de clases. Bueno, se puede explicar con ejemplos. Digamos que tenemos las clases Sofá y Cama, y ​​ambas implementan SleeperInterface.

 interface SleeprInterface { public function sleep() : void; } class Sofa implements SleeperInterface { public function sleep() : void { // sleeps on sofa } } class Bed implements SleeperInterface { public function sleep() : void { // sleeps on bed } }

Tanto los sofás como las camas tienen el mismo comportamiento para dormir. Ahora, necesitamos otros sofás y camas con funciones adicionales que informen a los usuarios sobre el seguimiento del sueño cuando duermen en los sofás o las camas. Con la herencia podemos resolver este problema así:

 class SmartSofa extends Sofa { public function sleep() : void { parent::sleep(); $this->sleepHours(); } } class SmartBed extends Window { public function sleep() : void { parent::sleep(); $this->sleepHours(); } }


Ahora tenemos 4 clases en total. Sin embargo, pudimos resolver este problema con 3 clases solo con el patrón Decorator. Así es cómo:

 class SmartSleeper implements SleeperInterface { private $sleeper; public function __construct(SleeperInterface $sleeper) { $this->sleeper = $sleeper; } public function sleep() : void { $this->sleeper->sleep(); $this->sleepHours(); } } $sofa = new Sofa(); $bed = new Bed(); $smartSofa = new SmartSleeper($sofa); $smartBed = new SmartSleeper($bed);

Aquí, hemos introducido un nuevo tipo de durmiente que actúa como un proxy pero con una funcionalidad adicional encima.

Aprovechando los patrones de diseño en Drupal 9

Si bien ya existen muchos patrones de diseño establecidos en Drupal antes de Drupal 9, ahora hay muchos más patrones que antes no estaban disponibles. Algunos de estos nuevos patrones reemplazan por completo a los antiguos, mientras que otros introducen algunas funciones nuevas en Drupal 9.
Los patrones de diseño utilizados en Drupal 9 incluyen:

  • Patrón de programación orientado a objetos (POO)
  • Inyecciones de dependencia
  • Patrón de fábrica
  • Patrón único

OOP no es realmente un patrón único, sino una forma completamente radical de conceptualizar y estructurar el código que va mucho más allá de los patrones de diseño. Es la base de muchos patrones de diseño de software populares que se usan hoy en día, incluidos los que se usan en Drupal 9. Se introdujo en Drupal 7, pero no se usó mucho y no era obligatorio. La situación en Drupal 9 ahora es diferente, se usa ampliamente y es obligatorio.

Inyecciones de dependencia

La inyección de dependencia es un patrón de diseño de software que le permitiría eliminar dependencias codificadas de forma rígida y también permitiría cambiarlas en tiempo de ejecución o en tiempo de compilación. Agregar inyección de dependencia es fácil y no interfiere con su código existente. Drupal 8 introdujo el concepto de servicios para desacoplar las funcionalidades reutilizables. core.services.yml es un ejemplo de inyección de dependencia en Drupal 9. Ya hemos discutido Factory Pattern y Singleton Pattern en PHP anteriormente.

Actualmente, en Drupal, la inyección de dependencia es el método preferido para acceder y usar servicios y debe usarse siempre que sea posible. En lugar de llamar al contenedor de servicios globales, los servicios se pasan como argumentos a un constructor o se inyectan a través de métodos setter. Pasar explícitamente los servicios de los que depende un objeto se denomina inyección de dependencia . En varios casos, las dependencias se pasan explícitamente en los constructores de clases.

Consulte esta página para encontrar todos los servicios disponibles en el núcleo de Drupal. Puede leer más sobre los servicios en la documentación de Drupal.

Consideremos el servicio 'entity_type.manager' como ejemplo para obtener el título del nodo con ID=1. Para inyectarlo en nuestro servicio personalizado, solo tenemos que tomar el nombre del servicio y pasarlo como argumento en el archivo my_module_name.services.yml como se muestra a continuación:

my_module_name.services.yml

 services: my_module_name.helper: class: Drupal\my_module_name\MyModuleHelper arguments: ['@entity_type.manager']

y luego en nuestra clase de servicio solo tenemos que obtener el servicio en el método _ _construct y almacenarlo en una variable como esta:

MiMóduloHelper.php

 <?php namespace Drupal\my_module_name; use Drupal\Core\Entity\EntityTypeManagerInterface; /** * MyModuleHelper is a simple example of a Drupal 9 service. */ class MyModuleHelper { /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * Part of the DependencyInjection magic happening here. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. */ public function __construct(EntityTypeManagerInterface $entity_type_manager) { $this->entityTypeManager = $entity_type_manager; } /** * Returns a title for node_id = 1. */ public function getFirstNodeTitle() { $node = $this->entityTypeManager->getStorage('node')->load(1); return $node->getTitle(); } }

y luego podríamos usar el servicio de administrador de tipo de entidad y obtener el título del nodo con nid=1 en el método getFirstNodeTitle.

Muchas gracias a Ankitha Shetty por sus ideas que nos ayudaron a actualizar el artículo.