Iniezione automatica delle dipendenze

In webman, l'iniezione automatica delle dipendenze è una funzionalità opzionale, che è disabilitata di default. Se hai bisogno dell'iniezione automatica delle dipendenze, si consiglia di utilizzare php-di; di seguito viene mostrato come webman si integri con php-di.

Installazione

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

Modifica la configurazione in config/container.php, il contenuto finale sarà il seguente:

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

config/container.php restituisce infine un'istanza del contenitore conforme alla specifica PSR-11. Se non desideri utilizzare php-di, puoi qui creare e restituire un'altra istanza di un contenitore conforme a PSR-11.

Iniezione del costruttore

Crea app/service/Mailer.php (se la directory non esiste, creala) con il seguente contenuto:

<?php
namespace app\service;

class Mailer
{
    public function mail($email, $content)
    {
        // Codice per l'invio dell'email omesso
    }
}

Il contenuto di app/controller/UserController.php sarà il seguente:

<?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');
    }
}

Normalmente, hai bisogno del seguente codice per completare l'istanza di app\controller\UserController:

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

Quando utilizzi php-di, lo sviluppatore non deve occuparsi della creazione manuale di Mailer nel controller; webman si occuperà automaticamente di questo. Se durante l'istanza di Mailer ci sono dipendenze di altre classi, webman istanzierà e inietterà anche queste automaticamente. Gli sviluppatori non necessitano di alcuna operazione di inizializzazione.

Attenzione
Solo le istanze create dal framework o php-di possono completare l'iniezione automatica delle dipendenze; le istanze create manualmente con new non possono completare l'iniezione delle dipendenze. Se necessiti dell'iniezione, devi utilizzare l'interfaccia support\Container in sostituzione della dichiarazione new, ad esempio:

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

// L'istanza creata con la parola chiave new non può essere iniettata
$user_service = new UserService;
// L'istanza creata con la parola chiave new non può essere iniettata
$log_service = new LogService($path, $name);

// Le istanze create da Container possono essere iniettate
$user_service = Container::get(UserService::class);
// Le istanze create da Container possono essere iniettate
$log_service = Container::make(LogService::class, [$path, $name]);

Iniezione tramite annotazioni

Oltre all'iniezione automatica delle dipendenze tramite costruttore, possiamo anche utilizzare l'iniezione tramite annotazioni. Continuando con l'esempio precedente, app\controller\UserController diventa:

<?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');
    }
}

Questo esempio utilizza l'annotazione @Inject per iniettare la dipendenza e l'annotazione @var per dichiarare il tipo dell'oggetto. Questo esempio ha lo stesso effetto dell'iniezione tramite costruttore, ma il codice è più leggibile.

Attenzione
webman non supporta l'iniezione dei parametri del controller nelle versioni precedenti a 1.4.6, ad esempio, il seguente codice non è supportato quando webman <= 1.4.6

<?php
namespace app\controller;

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

class UserController
{
    // Non supporta l'iniezione dei parametri del controller nelle versioni precedenti a 1.4.6
    public function register(Request $request, Mailer $mailer)
    {
        $mailer->mail('hello@webman.com', 'Hello and welcome!');
        return response('ok');
    }
}

Iniezione del costruttore personalizzata

A volte, i parametri passati al costruttore potrebbero non essere istanze di classi, ma stringhe, numeri, array e simili. Ad esempio, il costruttore di Mailer ha bisogno di passare l'IP del server smtp e la porta:

<?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)
    {
        // Codice per l'invio dell'email omesso
    }
}

In questo caso, non è possibile utilizzare direttamente l'iniezione automatica del costruttore, poiché php-di non può determinare quali siano i valori di $smtp_host e $smtp_port. Qui possiamo provare a fare un'iniezione personalizzata.

Aggiungi il seguente codice in config/dependence.php (crea il file se non esiste):

return [
    // ... qui sono state omesse altre configurazioni

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

In questo modo, quando l'iniezione delle dipendenze richiede di ottenere un'istanza di app\service\Mailer, verrà utilizzata automaticamente l'istanza di app\service\Mailer creata in questa configurazione.

Notiamo che in config/dependence.php abbiamo usato new per istanziare la classe Mailer, il che non presenta alcun problema in questo esempio; tuttavia, immagina che la classe Mailer faccia affidamento su altre classi o utilizzi l'iniezione tramite annotazioni. Utilizzare new per l'inizializzazione non consentirà l'iniezione automatica. La soluzione consiste nell'utilizzare l'iniezione tramite interfacce personalizzate, inizializzando la classe tramite i metodi Container::get(Classe) o Container::make(Classe, [parametri del costruttore]).

Iniezione tramite interfacce personalizzate

Nei progetti reali, preferiamo programmare orientati alle interfacce piuttosto che a classi specifiche. Ad esempio, in app\controller\UserController dovrebbe essere importata app\service\MailerInterface piuttosto che app\service\Mailer.

Definisci l'interfaccia MailerInterface.

<?php
namespace app\service;

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

Definisci l'implementazione dell'interfaccia 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)
    {
        // Codice per l'invio dell'email omesso
    }
}

Importa l'interfaccia MailerInterface piuttosto che l'implementazione specifica.

<?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');
    }
}

Configura config/dependence.php per definire l'interfaccia MailerInterface come segue.

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]);
    }
];

In questo modo, quando il business ha bisogno di utilizzare l'interfaccia MailerInterface, utilizzerà automaticamente l'implementazione Mailer.

Il vantaggio di programmare orientati alle interfacce è che quando abbiamo bisogno di sostituire un componente, non dobbiamo modificare il codice di business; basta modificare l'implementazione specifica in config/dependence.php. Questo è molto utile anche per i test unitari.

Altre iniezioni personalizzate

config/dependence.php può definire non solo le dipendenze delle classi, ma anche altri valori, come stringhe, numeri, array, ecc.

Ad esempio, config/dependence.php può essere definita come segue:

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

In questo caso, possiamo iniettare smtp_host e smtp_port nelle proprietà della classe tramite @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)
    {
        // Codice per l'invio dell'email omesso
        echo "{$this->smtpHost}:{$this->smtpPort}\n"; // Verrà stampato 192.168.1.11:25
    }
}

Nota: @Inject("key") utilizza le doppie virgolette.

Ulteriori informazioni

Si prega di fare riferimento alla documentazione di php-di