Inyeccion de dependencias
Usando la inyección de dependencia 5
La inyección de dependencias es un patrón de diseño de software mediante el cual una o más dependencias se inyectan o pasan por referencia a un objeto. Lo que esto significa exactamente en un nivel práctico se muestra en los siguientes dos ejemplos simples:
public function getTotalCustomers() { $database = new \PDO( … ); $statement = $database->query('SELECT …'); return $statement->fetchColumn(); }
Aquí, verá un ejemplo PHP simplificado, donde el objeto $ database se crea en el método getTotalCustomers. Esto significa que la dependencia del objeto de la base de datos se está bloqueando en un método de instancia de objeto. Esto permite un acoplamiento estrecho, que tiene varias desventajas, como la reutilización reducida y un posible efecto en todo el sistema causado por los cambios realizados en algunas partes del código.
Una solución a este problema es evitar los métodos con este tipo de dependencias inyectando una dependencia en un método, de la siguiente manera:
public function getTotalCustomers($database) { $statement = $database->query('SELECT ...'); return $statement->fetchColumn(); }
Aquí, un objeto $ database se pasa (inyecta) a un método. Eso es todo lo que es la inyección de dependencias: un concepto simple que hace que el código se acople libremente. Si bien el concepto es simple, puede no ser fácil implementarlo en plataformas grandes como Magento.
Magento tiene su propio administrador de objetos y mecanismo de inyección de dependencia que pronto veremos en detalle en las siguientes secciones:
- El administrador de objetos
- Inyección de dependencia
- Configurar preferencias de clase
- Usando tipos virtuales
El administrador de Objetos
La inicialización de objetos en Magento se realiza a través de lo que se llama el administrador de objetos. El administrador de objetos en sí es una instancia de la clase Magento \ Framework \ ObjectManager \ ObjectManager que implementa la clase Magento \ Framework \ ObjectManagerInterface. La clase ObjectManager define los siguientes tres métodos:
• create ($ type, array $ argumentos = []): Esto crea una nueva instancia de objeto
• get ($ type): recupera una instancia de objeto en caché
• configure (array $ configuration): esto configura la instancia de di
El administrador de objetos puede crear una instancia de una clase PHP, que puede ser un modelo, un auxiliar o un objeto de bloque. A menos que la clase con la que estamos trabajando ya haya recibido una instancia del administrador de objetos, podemos recibirla pasando ObjectManagerInterface al constructor de la clase, de la siguiente manera:
public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager ) { $this->_objectManager = $objectManager; } Usually, we don't have to take care of the constructor parameter's order in Magento. The following example will also enable us to fetch an instance of the object manager: public function __construct( $var1, \Magento\Framework\ObjectManagerInterface $objectManager, $var2 = [] ) { $this->_objectManager = $objectManager; }
Aunque todavía podemos usar PHP antiguo para instanciar un objeto como $ object = new \ Foggyline \ Di \ Model \ Object (), al usar el administrador de objetos, podemos aprovechar las características avanzadas de objetos de Magento, como la inyección automática de dependencia del constructor y representación de objetos.
Aquí hay algunos ejemplos del uso del método de creación del administrador de objetos para crear nuevos objetos:
$this->_objectManager->create('Magento\Sales\Model\Order') $this->_objectManager->create('Magento\Catalog\Model\Product\Image') $this->_objectManager->create('Magento\Framework\UrlInterface') $this->_objectManager->create('SoapServer', ['wsdl' => $url, 'options' => $options])
Los siguientes son algunos ejemplos del uso del método get del administrador de objetos para crear nuevos objetos:
$this->_objectManager->get('Magento\Checkout\Model\Session') $this->_objectManager->get('Psr\Log\LoggerInterface')->critical($e) $this->_objectManager->get('Magento\Framework\Escaper') $this->_objectManager->get('Magento\Sitemap\Helper\Data')
El método create del administrador de objetos siempre devuelve una nueva instancia de objeto, mientras que el método get devuelve un singleton.
Observe cómo algunos de los parámetros de cadena pasados para crear y obtener son en realidad nombres de interfaz y no estrictamente nombres de clase. Pronto veremos por qué esto funciona con nombres de clase y nombres de interfaz. Por ahora, es suficiente decir que funciona debido a la implementación de inyección de dependencia de Magento.
Inyección de dependencia
Hasta ahora, hemos visto cómo el administrador de objetos tiene control sobre la creación de instancias de dependencias. Sin embargo, por convención, no se supone que el administrador de objetos se use directamente en Magento. Más bien, debe usarse para cosas de nivel de sistema que inician Magento. Se nos recomienda usar el archivo etc / di.xml del módulo para crear instancias de objetos.
Analicemos una de las entradas di.xml existentes, como la que se encuentra en el archivo vendor / magento / module-admin-notify / etc / adminhtml / di.xml para el tipo Magento \ Framework \ Notification \ MessageList:
< type name="Magento\Framework\Notification\MessageList" > Magento\AdminNotification\Model\System \Message\Baseurl Magento\AdminNotification\Model\System\ Message\Security Magento\AdminNotification\Model\System\ Message\CacheOutdated Magento\AdminNotification\Model\ System\Message\Media\Synchronization\Error Magento\AdminNotification\Model\ System\Message\Media\Synchronization\Success < /type >
Básicamente, lo que esto significa es que cada vez que se crea una instancia de Magento \ Framework \ Notification \ MessageList, el parámetro de mensajes se pasa al constructor. El parámetro de mensajes se define como una matriz, que además consta de otros elementos de tipo cadena. En este caso, los valores de estos atributos de tipo de cadena son nombres de clase, como sigue:
- Magento \ Framework \ ObjectManager \ ObjectManager
- Magento \ AdminNotification \ Model \ System \ Message \ Baseurl
- Magento \ AdminNotification \ Model \ System \ Message \ Security
- Magento \ AdminNotification \ Model \ System \ Message \ CacheOutdatd
- Magento \ AdminNotification \ Model \ System \ Message \ Media \ Synchronization \ Error
- Magento \ AdminNotification \ Model \ System \ Message \ Media \ Synchronization \ Success
Si ahora observa el constructor de MessageList, verá que está definido de la siguiente manera:
public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, $messages = [] ) { //Method body here... } If we modify the MessageList constructor as follows, the code will work: public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, $someVarX = 'someDefaultValueX', $messages = [] ) { //Method body here... }
Después de la modificación:
After modification: public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, $someVarX = 'someDefaultValueX', $messages = [], $someVarY = 'someDefaultValueY' ) { //Method body here... } However, if we change the MessageList constructor to one of the following variations, the code will fail to work: public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, $Messages = [] ) { //Method body here... }
Otra variación es la siguiente:
public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, $_messages = [] ) { //Method body here... }
El nombre del parámetro $ messages en el constructor de la clase PHP tiene que coincidir exactamente con el nombre del argumento dentro de la lista de argumentos de di.xml. El orden de los parámetros en el constructor realmente no importa tanto como su nombre.
Mirando más allá en el constructor de MessageList, si ejecutamos func_get_args en algún lugar dentro de él, la lista de elementos dentro del parámetro $ messages coincidirá y excederá la que se muestra en vendor / magento / module-admin-notify / etc / adminhtml / di.xml. Esto es así porque la lista no es final, ya que Magento recopila las definiciones DI de toda la plataforma y las fusiona. Entonces, si otro módulo está modificando el tipo de Lista de mensajes, las modificaciones se reflejarán.
Si realizamos una búsqueda de cadenas dentro de todos los archivos di.xml en toda la base de códigos de Magento para , esto generará algunos archivos di.xml adicionales que tienen sus propias adiciones a el tipo de Lista de mensajes, de la siguiente manera:
//vendor/magento/module-indexer/etc/adminhtml/di.xml < type name="Magento\Framework\Notification\MessageList" > Magento\Indexer\Model\Message \Invalid < /type > //vendor/magento/module-tax/etc/adminhtml/di.xml < type name="Magento\Framework\Notification\MessageList" > Magento \Tax\Model\System\Message\Notifications < /type >
Lo que esto significa es que los elementos de cadena Magento \ Indexer \ Model \ Message \ Invalid y Magento \ Tax \ Model \ System \ Message \ Notificaciones se agregan al argumento de mensajes y se ponen a disposición dentro del constructor MessageList.
En el ejemplo DI anterior, solo teníamos el parámetro $ messages definido como un argumento del tipo de matriz, y el resto eran sus elementos de matriz.
Echemos un vistazo a un ejemplo DI para otra definición de tipo. Esta vez, es el que se encuentra en el archivo vendor / magento / module-backend / etc / di.xml y que se define de la siguiente manera:
< type name="Magento\Backend\Model\Url" > Magento\Backend\Model\Url\ScopeResolver Magento\Backend\Model\Auth\Session\Proxy Magento\Framework\Data\Form\FormKey\Proxy Magento\Store\Model\ScopeInterface::SCOPE_STORE Magento\Backend\Helper\Data\Proxy < /type > Here, you will see a type with several different arguments passed to the constructor of the Magento\Backend\Model\Url class. If you now take a look at the constructor of the Url class, you will see that it is defined in the following way: public function __construct( \Magento\Framework\App\Route\ConfigInterface $routeConfig, \Magento\Framework\App\RequestInterface $request, \Magento\Framework\Url\SecurityInfoInterface $urlSecurityInfo, \Magento\Framework\Url\ScopeResolverInterface $scopeResolver, \Magento\Framework\Session\Generic $session, \Magento\Framework\Session\SidResolverInterface $sidResolver, \Magento\Framework\Url\RouteParamsResolverFactory $routeParamsResolverFactory, \Magento\Framework\Url\QueryParamsResolverInterface $queryParamsResolver, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, $scopeType, \Magento\Backend\Helper\Data $backendHelper, \Magento\Backend\Model\Menu\Config $menuConfig, \Magento\Framework\App\CacheInterface $cache, \Magento\Backend\Model\Auth\Session $authSession, \Magento\Framework\Encryption\EncryptorInterface $encryptor, \Magento\Store\Model\StoreFactory $storeFactory, \Magento\Framework\Data\Form\FormKey $formKey, array $data = [] ) { //Method body here... }
El método __construct aquí claramente tiene más parámetros que los definidos en el archivo di.xml. Lo que esto significa es que las entradas de argumento de tipo en di.xml no cubren necesariamente todos los parámetros de clase __construct. Los argumentos que se definen en di.xml simplemente imponen los tipos de parámetros individuales definidos en la propia clase PHP. Esto funciona siempre que los parámetros di.xml sean del mismo tipo o descendientes del mismo tipo.
Idealmente, no pasaríamos el tipo de clase sino la interfaz en el constructor de PHP y luego estableceríamos el tipo en di.xml. Aquí es donde el tipo, preferencia y virtualType juegan un papel importante en di.xml. Hemos visto el papel del tipo. Ahora, sigamos adelante y veamos qué hace la preferencia.
Configurar preferencias de clase
Una gran cantidad de las clases principales de Magento pasan interfaces alrededor de los constructores. El beneficio de esto es que el administrador de objetos, con la ayuda de di.xml, puede decidir qué clase realmente creará una instancia para una interfaz determinada.
Imaginemos la clase Foggyline \ Di \ Console \ Command \ DiTestCommand con un constructor, de la siguiente manera:
public function __construct( \Foggyline\Di\Model\TestInterface $myArg1, $myArg2, $name = null ) { //Method body here... }
Observe cómo $ myArg1 se insinúa como la interfaz \ Foggyline \ Di \ Model \ TestInterface. El administrador de objetos sabe que debe buscar en todo el di.xml para posibles definiciones de preferencias.
Podemos definir preferencias dentro del archivo di.xml del módulo, de la siguiente manera:
< preference for="Foggyline\Di\Model\TestInterface" type="Foggyline\Di\Model\Cart"/>
Aquí, básicamente estamos diciendo que cuando alguien solicita una instancia de Foggyline \ Di \ Model \ TestInterface, le da una instancia del objeto Foggyline \ Di \ Model \ Cart. Para que esto funcione, la clase Cart tiene que implementar TestInterface. Una vez que la definición de preferencia está en su lugar, $ myArg1 que se muestra en el ejemplo anterior se convierte en un objeto de la clase Cart.
Además, el elemento de preferencia no está reservado solo para señalar las clases preferidas para algunas interfaces. Podemos usarlo para establecer la clase preferida para alguna otra clase.
Ahora, echemos un vistazo a la clase Foggyline \ Di \ Console \ Command \ DiTestCommand con un constructor:
public function __construct( \Foggyline\Di\Model\User $myArg1, $myArg2, $name = null ) { //Method body here... }
Observe cómo $ myArg1 ahora se escribe como la clase \ Foggyline \ Di \ Model \ User. Como en el ejemplo anterior, el administrador de objetos buscará en di.xml las posibles definiciones de preferencia.
Definamos el elemento de preferencia dentro del archivo di.xml del módulo, de la siguiente manera:
< preference for="\Foggyline\Di\Model\User" type="Foggyline\Di\Model\Cart"/>
Lo que dice esta definición de preferencia es que siempre que se solicite una instancia de la clase Usuario, pase una instancia del objeto Carrito. Esto funcionará solo si la clase Cart se extiende desde el Usuario. Esta es una forma conveniente de reescribir una clase, donde la clase se pasa directamente a otro constructor de clase en lugar de la interfaz.
Dado que los parámetros de clase __construct pueden insinuarse como clases o interfaces y manipularse aún más a través de la definición de preferencia di.xml, surge una pregunta sobre qué es mejor. ¿Es mejor usar interfaces o clases específicas? Si bien la respuesta puede no ser completamente clara, siempre es preferible usar interfaces para especificar las dependencias que estamos inyectando en el sistema.
Usando tipos virtuales
Junto con el tipo y la preferencia, hay otra característica poderosa de di.xml que podemos usar. El elemento virtualType nos permite definir tipos virtuales. Crear un tipo virtual es como crear una subclase de una clase existente, excepto por el hecho de que se hace en di.xml y no en código.
Los tipos virtuales son una forma de inyectar dependencias en algunas de las clases existentes sin afectar a otras clases. Para explicar esto a través de un ejemplo práctico, echemos un vistazo al siguiente tipo virtual definido en el archivo app / etc / di.xml:
message < /virtualType > < type name="Magento\Framework\Message\Session" > Magento\Framework\Message\Session\Storage < /type >
La definición de virtualType en el ejemplo anterior es Magento \ Framework \ Message \ Session \ Storage, que se extiende desde Magento \ Framework \ Session \ Storage y sobrescribe el parámetro del espacio de nombres al valor de la cadena del mensaje. En virtualType, el atributo de nombre define el nombre único global del tipo virtual, mientras que el atributo de tipo coincide con la clase PHP real en la que se basa el tipo virtual.
Ahora, si observa la definición de tipo, verá que su argumento de almacenamiento está establecido en el objeto de Magento \ Framework \ Message \ Session \ Storage. El archivo Session \ Storage es en realidad un tipo virtual. Esto permite personalizar Message \ Session sin afectar a otras clases que también declaran una dependencia en Session \ Storage.
Los tipos virtuales nos permiten cambiar efectivamente el comportamiento de una dependencia cuando se usa en una clase específica.
Resumen
En este capítulo, analizamos el administrador de objetos y la inyección de dependencias, que son los fundamentos de la administración de objetos de Magento. Aprendimos el significado del tipo y los elementos de preferencia de la inyección de dependencia y cómo usarlos para manipular los parámetros de construcción de clases. Aunque hay mucho más que decir sobre la inyección de dependencia en Magento, la información presentada debería ser suficiente y ayudarnos con otros aspectos de Magento.
En el próximo capítulo, ampliaremos nuestro viaje a di.xml a través del concepto de complementos.