Patrones de diseño en Magento

por David Abad
Patrones diseño: Magento

Los patrones de diseño son soluciones predefinidas para resolver problemas que suelen darse con frecuencia en el desarrollo de software. Magento incluye multitud de estos patrones para facilitar las tareas de programación.

En este artículo se resumen los patrones de diseño más importantes, que todo desarrollador debe conocer para programar de forma eficiente en Magento.

Inyección de dependencias

La inyección de dependencias es un patrón de diseño que permite utilizar una instancia de cierta clase A desde una clase B. 

Inyección en constructor

Al realizar una inyección en el constructor, Magento se encarga de facilitarnos instancias de las clases/interfaces definidas como parámetros, utilizando la lógica del ObjectManager: 

public function __construct(\Magento\Catalog\Api\ProductRepositoryInterface $repo) {
   $this->_repo = $repo;
}

Inyección en método

Este tipo de inyección indica que la lógica de una función concreta depende de otra clase, que se indicará como parámetro de entrada:

public function processCommand(\Magento\Backend\Model\Menu\Builder\AbstractCommand $command) {
    … 
}

Singleton

La inyección de dependencias de Magento se basa en el patrón Singleton. Es decir, cada clase inyectada se instancia una única vez y esta misma instancia es utilizada en todas las clases que la inyectan. 

Este patrón es aplicado de forma automática al utilizar la inyección de dependencias. Si se desean obtener nuevas instancias del objeto, puede utilizarse el patrón Factory o deshabilitar el patrón Singleton para clases concretas indicándolo en el fichero etc/di.xml mediante la propiedad “shared”:

<type name="MiVendor\MiModulo\Model\MiModelo" shared="false" />

Factory

Si se necesita obtener una o más nuevas instancias de una clase, se debe utilizar el patrón Factory. Este patrón permite inyectar un objeto en el constructor para generar instancias de la clase requerida:

public function __construct(\Magento\Customer\Model\CustomerFactory $customerFactory) 
{
   $this->_customer1 = $customerFactory->create();
   $this->_customer2 = $customerFactory->create();
}

A la hora de crear una nueva instancia de un objeto mediante la función create(), podemos incluir un array como parámetro de entrada, donde se especifiquen los argumentos a utilizar en el constructor de la clase correspondiente:

$modeloFactory->create(['parametro' => 'valor']);

XML Arguments

Magento permite definir los valores por defecto que deben usarse como argumentos del constructor de cada clase, a la hora de instanciarse. Esta información puede definirse a nivel de XML en el fichero etc/di.xml del módulo correspondiente, donde pueden incluirse valores de diferentes tipos:

  • xsi:type=”object”
  • xsi:type=”string”
  • xsi:type=”number”
  • xsi:type=”boolean”
  • xsi:type=”null”
  • xsi:type=”const”
  • xsi:type=”init_parameter”
  • xsi:type=”array”
<type name="Vendor\Modulo\Model\Modelo1">
   <arguments>
      <argument name="miArg1" xsi:type="string">miValor</argument>
      <argument name="miArg2" xsi:type="object">Vendor\Modulo\Dir\MiClase</argument>
      <argument name="miArg3" xsi:type="const">Vendor\Modulo\Dir\MiClase::MI_CONSTANTE</argument>
      <argument name="miArg4" xsi:type="boolean">true</argument>
      <argument name="miArg5" xsi:type="array">
           <item name="misDatos" xsi:type="array">
              <item name="miDato1" xsi:type="string">Mi nombre</item>
              <item name="miDato2" xsi:type="string">Mi apellido</item>
           </item>
      </argument>
   </arguments>        
</type>

XML Virtual Types

Los datos definidos como XML Arguments afectan a nivel global en la aplicación. Si queremos reemplazar argumentos de una clase sólo en determinados casos, podemos utilizar los virtual types. Estos virtual types generan clases “ficticias” a partir de otras ya existentes modificando sus argumentos. 

Las clases virtuales solo son usables desde ficheros XML (no son inyectables en los constructores de las clases).

<virtualType name="Magento\Core\Model\Session\Storage" type="Magento\Framework\Session\Storage">
    <arguments>
        <argument name="namespace" xsi:type="string">core</argument>
    </arguments>
</virtualType>

<type name="Magento\Framework\Session\Generic">
    <arguments>
        <argument name="storage" xsi:type="object">Magento\Core\Model\Session\Storage</argument>
    </arguments>
</type>

Proxy

Cuando la inicialización de una clase a inyectar es muy pesada y no sabemos si realmente va a ser utilizada, puede utilizarse el patrón Proxy, que permite inyectar una clase pero solo se inicializará si finalmente es utilizada. 

Por ejemplo, si en nuestra clase \Vendor\Modulo\Model\Modelo inyectamos una clase muy pesada de inicializar ($heavy), podemos definir un Proxy para la clase, indicándolo en el fichero di.xml del módulo correspondiente:

public function __construct(\Vendor\Modulo\Helper\HeavyHelper $heavy) {
   $this->_heavy = $heavy;
}
<type name="Vendor\Modulo\Model\Modelo">
   <arguments>
      <argument name="heavy" xsi:type="object">Vendor\Modulo\Helper\HeavyHelper\Proxy</argument>
   </arguments>        
</type>

Class Rewrites

Magento permite sobreescribir la lógica de clases nativas de Magento mediante class rewrites, para que el proceso de compilación las reemplace por clases personalizadas. Para ello se debe indicar la clase a sobreescribir en el fichero etc/di.xml del módulo correspondiente:

<preference for="Magento\Store\Block\Switcher" type="MiVendor\MiModulo\Block\Store\Switcher" />

Para minimizar problemas, es recomendable que la clase personalizada extienda de la original. Aunque, dentro de lo posible, es recomendable prescindir de este tipo de práctica y utilizar plugins para personalizar la lógica de clases nativas de Magento.

Plugins

Magento permite modificar el comportamiento de sus clases nativas mediante plugins, que ejecutan una lógica personalizada antes y/o después de una función. 

Existen algunos casos para los que no se pueden utilizar plugins:

  • Métodos finales
  • Clases finales
  • Métodos no públicos
  • Métodos estáticos
  • Constructores
  • Tipos virtuales (virtualType)
  • Objetos instanciados antes que Magento\Framework\Interception

Las clases que almacenan la lógica de los plugins se crean en el directorio “Plugin” del módulo correspondiente y se definen en los ficheros di.xml:

<type name="<Clase observada>">
   <plugin name="<Nombre del plugin>" type="<Clase del plugin>" sortOrder="1" disabled="false" />
</type>

Plugin before

Los plugins before ejecutan su lógica antes de lanzar una función y reciben como parámetros de entrada:

  1. Instancia de la clase interceptada.
  2. Parámetros de entrada de la función interceptada.

Como resultado, los plugin before pueden devolver (opcionalmente) un array con los parámetros de entrada modificados.

public function beforeSetName(\Magento\Catalog\Model\Product $subject, $name) 
{
   $nameModificado = strtoupper($name);
   return [$nameModificado];
}

Plugin after

Los plugins after ejecutan su lógica después de lanzar una función y reciben como parámetros de entrada:

  1. Instancia de la clase interceptada.
  2. Resultado devuelto por la función interceptada.
  3. Parámetros de entrada de la función interceptada. (Opcional. No es necesario incluirlos todos)

Como resultado, estos plugin devuelven el resultado de la función interceptada, pudiendo alterar su valor.

public function afterLogin(\Magento\Backend\Model\Auth $authModel, $result, $username) 
{
   $resultModificado = strtoupper($result);
   return $resultModificado;
}

Plugin around

Los plugins around ejecutan su lógica antes y después de lanzar una función. Como parámetros de entrada reciben:

  1. Instancia de la clase interceptada.
  2. Objeto “callable” para llamar a la función interceptada original.
  3. Parámetros de entrada de la función interceptada. (Opcional. No es necesario incluirlos todos)
public function aroundSave(\Vendor\Modulo\Model\MiClase $subject, callable $proceed, ...$args) 
{
    // Lógica antes de función original
    $result = $proceed(...$args);
    // Lógica despues de función original
    
    return $result;
}

Event / Observer

Magento implementa el patrón publish-subscribe en forma de Eventos y Observadores.

Durante la ejecución del código de Magento se lanzan diferentes eventos, que pueden ser atendidos por observadores que ejecutan una lógica como respuesta. Estos eventos se lanzan utilizando el objeto \Magento\Framework\Event\Manager y pueden incluir parámetros que recibirá el observer:

$this->eventManager->dispatch('<Código del evento>', ['myEventData' => $eventData]);

Para definir un observador para un evento concreto, es necesario crear una clase que implemente \Magento\Framework\Event\ObserverInterface y suscribirla al evento correspondiente en el fichero events.xml:

class MiObserver implements \Magento\Framework\Event\ObserverInterface
{
   public function execute(\Magento\Framework\Event\Observer $observer)
   {
       $myEventData = $observer->getData('myEventData');
   }
}
<event name="<Código del evento>">
    <observer name="<Nombre del observador>" instance="MiVendor\MiModulo\Observer\MiObserver" />
</event>

Déjanos tu email para recibir contenido interesante en tu bandeja de entrada, cada mes.

¡No hacemos spam!

Otros artículos