Magento 2 utilise le principe d’injection de dépendances comme alternative à la classe finale Mage de Magento 1. Cela veut dire que si une classe A dépend du service B, B doit être injecté directement dans le constructeur de A, ce n’est plus à A de récupérer la dépendance B au moment où il en a besoin.

Cela implique inévitablement que B doit être instancié avant d’être injecté dans A. Cela n’a rien de problématique en soi, par contre imaginons que l’instanciation de B soit relativement lourde en terme de ressource et que B possède également des dépendances, qui elles-mêmes possèdent des dépendances, qui elles-mêmes… Vous l’aurez compris, cela peut commencer à devenir non négligeable, surtout (et c’est là que la problématique devient intéressante) si A a besoin de B uniquement dans certaines conditions (selon l’état d’une variable, l’état d’un service, le calcul d’un résultat, ou autre), l’instanciation de B devient donc inutile si A n’en a jamais besoin. Les classes de type Proxy sont présentes pour répondre à cette problématique.

Les Proxies

Les Proxies sont des classes qui sont générées automatiquement dans Magento 2. Par conséquent, elles peuvent être utilisées, en théorie, sur n’importe quelle classe. Une classe Proxy n’est rien d’autre qu’une version “lazy-loaded” de la classe qu’elle étend. Prenons l’exemple du code suivant :

<?php
// Exemple d'injection de dépendance

class SlowLoading
{
    public function __construct()
    {
        // ... Do something resource intensive
    }

    public function getValue()
    {
        return 'SlowLoading value';
    }
}

class FastLoading
{
    protected $slowLoading;

    public function __construct(
        SlowLoading $slowLoading
    ){
        $this->slowLoading = slowLoading;
    }

    public function getFastValue()
    {
        return 'FastLoading value';
    }

    public function getSlowValue()
    {
        return $this->slowLoading->getValue();
    }
}

Ce code met en évidence deux classes. La première, SlowLoading, possède un constructeur dont le contenu prend un certain temps. Cette classe est une dépendance de la classe FastLoading car elle est injectée dans son constructeur. Par contre, comme on peut le voir, la classe FastLoading n’a besoin de cette dépendance que si l’on fait appel à la méthode getSlowValue().

Injection

La solution est donc d’injecter la version Proxy de SlowLoading. Cela aura pour effet que l’instanciation réelle de SlowLoading ne sera faite qu’au moment de l’appel de

$this->slowLoading->getValue().

Le constructeur de FastLoading devient donc à présent :

<?php
// Injection d'une classe Proxy
public function __construct(
        SlowLoading\Proxy $slowLoading
    ){
        $this->slowLoading = slowLoading;
    }

Au moment de l’exécution du code, si la dite classe Proxy n’existe pas, Magento va automatiquement la générer dans le dossier generated (Magento >= v2.2, var/generation pour <v2.2), de la même manière que les Factory. Cette classe Proxy va donc utiliser l’ObjectManager de Magento au moment de l’appel à getSlowValue() pour instancier réellement SlowLoading. Voici un petit extrait de la classe générée pour bien comprendre le principe du Proxy :

<?php
// Extrait d'une classe Proxy
class Proxy extends SlowLoading implements NoninterceptableInterface
{
    /* ... properties and construct */

    protected function _getSubject()
    {
        if (!$this->_subject) {
            $this->_subject = true === $this->_isShared
                ? $this->_objectManager->get($this->_instanceName)
                : $this->_objectManager->create($this->_instanceName);
        }
        return $this->_subject;
    }

    public function getValue()
    {
        return $this->_getSubject()->getValue();
    }
}

L’injection du Proxy peut également se faire via une configuration XML d’injection de dépendance sans modifier le code de la classe grâce au fichier di.xml :

<!-- Configuration d'injection de dépendance di.xml -->
<type name="FastLoading">
    <arguments>
        <argument name="slowLoading" xsi:type="object">SlowLoading\Proxy</argument>
    </arguments>
</type>

Exemple d’utilisation

Un exemple typique d’utilisation est l’ajout d’une commande dans le pool des commandes de l’utilitaire bin/magento. Lorsque l’on va afficher la liste des commandes disponibles, toutes les classes “Command” seront instanciées ainsi que leurs dépendances. Par contre, bien évidemment, si on n’exécute pas une commande de la liste, toutes les dépendances auront été instanciées et injectées pour rien. Selon moi, la bonne pratique est donc d’utiliser systématiquement des classes Proxy dans les dépendances d’une commande bin/magento.

J’ai eu le cas sur la classe ProductRepository qui était injectée dans une commande (CLI). Cela générait une erreur lorsqu’on exécutait bin/magento sur un Magento pas encore installé (setup:install) car une des dépendances du ProductRepository faisait appel à la configuration. J’ai donc résolu le problème en utilisant le Proxy.

Documentation Magento : http://devdocs.magento.com/guides/v2.3/extension-dev-guide/proxies.html