Injection de dépendance automatique

Dans Webman, l'injection de dépendance automatique est une fonctionnalité optionnelle, désactivée par défaut. Si vous avez besoin de cette fonctionnalité, il est recommandé d'utiliser php-di. Voici comment utiliser Webman avec php-di.

Installation

composer require psr/container ^1.1.1 php-di/php-di ^6.3 doctrine/annotations ^1.14

Modifiez le fichier de configuration config/container.php, dont le contenu final sera le suivant :

$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();

config/container.php retourne finalement une instance de conteneur conforme à la norme PSR-11. Si vous ne souhaitez pas utiliser php-di, vous pouvez créer et retourner une autre instance de conteneur conforme à la norme PSR-11 ici.

Injection par constructeur

Créez app/service/Mailer.php (veuillez créer le répertoire si nécessaire) avec le contenu suivant :

<?php
namespace app\service;

class Mailer
{
    public function mail($email, $content)
    {
        // Code d'envoi d'email omis
    }
}

Le contenu de app/controller/UserController.php sera le suivant :

<?php
namespace app\controller;

use support\Request;
use app\service\Mailer;

class UserController
{
    private $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function register(Request $request)
    {
        $this->mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

Normalement, vous avez besoin du code suivant pour instancier app\controller\UserController :

$mailer = new Mailer;
$user = new UserController($mailer);

Lorsque vous utilisez php-di, le développeur n'a plus besoin d'instancier manuellement Mailer dans le contrôleur, Webman s'en charge automatiquement pour vous. Si lors de l'instanciation de Mailer, il y a d'autres dépendances de classes, Webman les instanciera et les injectera automatiquement. Le développeur n'a pas besoin d'effectuer d'initialisation.

Attention
Les instances doivent être créées par le framework ou php-di pour que l'injection de dépendance automatique fonctionne. Les instances créées manuellement avec new ne peuvent pas être injectées. Pour injecter, utilisez l'interface support\Container pour remplacer l'instruction new, par exemple :

use app\service\UserService;
use app\service\LogService;
use support\Container;

// L'instance créée avec le mot clé new ne peut pas être injectée
$user_service = new UserService;
// L'instance créée avec le mot clé new ne peut pas être injectée
$log_service = new LogService($path, $name);

// L'instance créée par le Container peut être injectée
$user_service = Container::get(UserService::class);
// L'instance créée par le Container peut être injectée
$log_service = Container::make(LogService::class, [$path, $name]);

Injection par annotation

En plus de l'injection automatique par constructeur, nous pouvons également utiliser l'injection par annotation. Poursuivant l'exemple précédent, modifiez app\controller\UserController comme suit :

<?php
namespace app\controller;

use support\Request;
use app\service\Mailer;
use DI\Annotation\Inject;

class UserController
{
    /**
     * @Inject
     * @var Mailer
     */
    private $mailer;

    public function register(Request $request)
    {
        $this->mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

Cet exemple injecte via l'annotation @Inject et déclare le type d'objet avec l'annotation @var. Cet exemple a le même effet que l'injection par constructeur, mais le code est plus concis.

Attention
Webman ne prend pas en charge l'injection de paramètres de contrôleur avant la version 1.4.6. Par exemple, le code suivant n'est pas pris en charge lorsque Webman <= 1.4.6 :

<?php
namespace app\controller;

use support\Request;
use app\service\Mailer;

class UserController
{
    // L'injection de paramètres de contrôleur n'est pas supportée dans la version 1.4.6
    public function register(Request $request, Mailer $mailer)
    {
        $mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

Injection personnalisée par constructeur

Parfois, les paramètres passés au constructeur ne sont pas des instances de classe, mais des chaînes, des nombres, des tableaux, etc. Par exemple, le constructeur de Mailer peut nécessiter l'adresse IP et le port du serveur SMTP :

<?php
namespace app\service;

class Mailer
{
    private $smtpHost;

    private $smtpPort;

    public function __construct($smtp_host, $smtp_port)
    {
        $this->smtpHost = $smtp_host;
        $this->smtpPort = $smtp_port;
    }

    public function mail($email, $content)
    {
        // Code d'envoi d'email omis
    }
}

Dans ce cas, l'injection automatique par constructeur présentée précédemment ne peut pas être utilisée directement, car php-di ne peut pas déterminer quelles sont les valeurs de $smtp_host et $smtp_port. Dans ce cas, vous pouvez essayer l'injection personnalisée.

Ajoutez le code suivant dans config/dependence.php (veuillez créer le fichier si nécessaire) :

return [
    // ... D'autres configurations omises ici

    app\service\Mailer::class => new app\service\Mailer('192.168.1.11', 25);
];

Ainsi, lorsque l'injection de dépendance doit récupérer une instance de app\service\Mailer, celle-ci utilisera automatiquement l'instance de app\service\Mailer créée dans cette configuration.

Nous remarquons que dans config/dependence.php, l'utilisation de new pour instancier la classe Mailer ne pose aucun problème dans cet exemple. Mais imaginez que si la classe Mailer dépendait d'autres classes ou utilisait l'injection par annotation, l'initialisation avec new ne bénéficierait pas de l'injection automatique. La solution consiste à utiliser l'injection d'interface personnalisée en initialisant la classe via Container::get(Classe) ou Container::make(Classe, [paramètres du constructeur]).

Injection d'interface personnalisée

Dans les projets réels, nous préférons souvent programmer par interface plutôt que par classes concrètes. Par exemple, dans app\controller\UserController, il devrait importer app\service\MailerInterface plutôt que app\service\Mailer.

Définissez l'interface MailerInterface.

<?php
namespace app\service;

interface MailerInterface
{
    public function mail($email, $content);
}

Définissez l'implémentation de MailerInterface.

<?php
namespace app\service;

class Mailer implements MailerInterface
{
    private $smtpHost;

    private $smtpPort;

    public function __construct($smtp_host, $smtp_port)
    {
        $this->smtpHost = $smtp_host;
        $this->smtpPort = $smtp_port;
    }

    public function mail($email, $content)
    {
        // Code d'envoi d'email omis
    }
}

Importez l'interface MailerInterface et non l'implémentation concrète.

<?php
namespace app\controller;

use support\Request;
use app\service\MailerInterface;
use DI\Annotation\Inject;

class UserController
{
    /**
     * @Inject
     * @var MailerInterface
     */
    private $mailer;

    public function register(Request $request)
    {
        $this->mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

Définissez l'implémentation de l'interface MailerInterface dans config/dependence.php comme suit.

use Psr\Container\ContainerInterface;
return [
    app\service\MailerInterface::class => function(ContainerInterface $container) {
        return $container->make(app\service\Mailer::class, ['smtp_host' => '192.168.1.11', 'smtp_port' => 25]);
    }
];

Ainsi, lorsque le système doit utiliser l'interface MailerInterface, il utilisera automatiquement l'implémentation Mailer.

L'avantage de la programmation par interface est qu'en cas de besoin de remplacer un composant, il n'est pas nécessaire de modifier le code métier, il suffit de modifier l'implémentation concrète dans config/dependence.php. Cela est également très utile pour les tests unitaires.

Autres injections personnalisées

Dans config/dependence.php, vous pouvez également définir d'autres valeurs, telles que des chaînes, des nombres, des tableaux, etc.

Par exemple, config/dependence.php peut être défini comme suit :

return [
    'smtp_host' => '192.168.1.11',
    'smtp_port' => 25
];

À ce moment, nous pouvons injecter smtp_host et smtp_port dans les propriétés de la classe via @Inject.

<?php
namespace app\service;

use DI\Annotation\Inject;

class Mailer
{
    /**
     * @Inject("smtp_host")
     */
    private $smtpHost;

    /**
     * @Inject("smtp_port")
     */
    private $smtpPort;

    public function mail($email, $content)
    {
        // Code d'envoi d'email omis
        echo "{$this->smtpHost}:{$this->smtpPort}\n"; // Affichera 192.168.1.11:25
    }
}

Remarque : dans @Inject("key"), il y a des guillemets doubles.

Plus de contenu

Veuillez consulter le manuel php-di