Abhängigkeiten automatisch injizieren

In Webman ist die automatische Injektion von Abhängigkeiten eine optionale Funktion, die standardmäßig deaktiviert ist. Wenn Sie die automatische Injektion von Abhängigkeiten benötigen, wird empfohlen, php-di zu verwenden. Im Folgenden finden Sie die Verwendung von Webman in Kombination mit php-di.

Installation

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

Ändern Sie die Konfiguration in config/container.php, der endgültige Inhalt sollte wie folgt aussehen:

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

In config/container.php wird schließlich eine Container-Instanz zurückgegeben, die dem PSR-11 Standard entspricht. Wenn Sie php-di nicht verwenden möchten, können Sie hier eine andere Container-Instanz erstellen und zurückgeben, die dem PSR-11 Standard entspricht.

Konstruktorinjektion

Erstellen Sie app/service/Mailer.php (falls das Verzeichnis nicht existiert, erstellen Sie es bitte selbst) mit folgendem Inhalt:

<?php
namespace app\service;

class Mailer
{
    public function mail($email, $content)
    {
        // Der Code zum Senden von E-Mails ist hier weggelassen
    }
}

Der Inhalt von app/controller/UserController.php sieht wie folgt aus:

<?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 und willkommen!');
        return response('ok');
    }
}

Normalerweise muss der folgende Code geschrieben werden, um eine Instanz von app\controller\UserController zu erstellen:

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

Wenn php-di verwendet wird, müssen Entwickler Mailer im Controller nicht manuell instanziieren; Webman wird dies automatisch für Sie erledigen. Wenn beim Instanziieren von Mailer andere Klassen abhängen, wird Webman diese auch automatisch instanziieren und injizieren. Die Entwickler benötigen keine weiteren Initialisierungsarbeiten.

Hinweis
Nur Instanzen, die vom Framework oder php-di erzeugt wurden, können die automatische Abhängigkeitsinjektion durchführen. Manuell mit new erstellte Instanzen können dies nicht. Wenn eine Injektion erforderlich ist, verwenden Sie bitte das support\Container Interface, um die new Anweisung zu ersetzen, zum Beispiel:

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

// Mit new erstellte Instanzen können nicht injiziert werden
$user_service = new UserService;
// Mit new erstellte Instanzen können nicht injiziert werden
$log_service = new LogService($path, $name);

// Instanzen, die mit Container erstellt wurden, können injiziert werden
$user_service = Container::get(UserService::class);
// Instanzen, die mit Container erstellt wurden, können injiziert werden
$log_service = Container::make(LogService::class, [$path, $name]);

Annotationsinjektion

Neben der Konstruktorinjektion können wir auch Annotationsinjektion verwenden. Führen Sie das oben genannte Beispiel fort, ändern Sie app\controller\UserController wie folgt:

<?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 und willkommen!');
        return response('ok');
    }
}

Dieses Beispiel injiziert mit dem @Inject Annotation, und der Typ des Objekts wird durch die @var Annotation deklariert. Dieses Beispiel hat die gleiche Wirkung wie die Konstruktorinjektion, ist jedoch im Code kompakter.

Hinweis
Webman unterstützt vor Version 1.4.6 keine Parameterinjektion in Controllern. Der folgende Code wird bei Webman <= 1.4.6 nicht unterstützt.

<?php
namespace app\controller;

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

class UserController
{
    // Vor Version 1.4.6 wurde die Parameterinjektion im Controller nicht unterstützt
    public function register(Request $request, Mailer $mailer)
    {
        $mailer->mail('hello@webman.com', 'Hello und willkommen!');
        return response('ok');
    }
}

Benutzerdefinierte Konstruktorinjektion

Manchmal sind die Parameter, die im Konstruktor übergeben werden, möglicherweise keine Instanzen von Klassen, sondern Strings, Zahlen, Arrays und andere Daten. Zum Beispiel benötigt der Konstruktor von Mailer die IP und den Port des SMTP-Servers:

<?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)
    {
        // Der Code zum Senden von E-Mails ist hier weggelassen
    }
}

In solchen Fällen kann die zuvor beschriebene automatische Konstruktorinjektion nicht direkt verwendet werden, da php-di nicht bestimmen kann, was die Werte von $smtp_host und $smtp_port sind. In diesem Fall können Sie versuchen, benutzerdefinierte Injektionen zu verwenden.

Fügen Sie in config/dependence.php (falls die Datei nicht existiert, erstellen Sie sie bitte selbst) den folgenden Code hinzu:

return [
    // ... Andere Konfigurationen werden hier ignoriert

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

Wenn die Abhängigkeitsinjektion eine Instanz von app\service\Mailer anfordert, wird automatisch diese in der Konfiguration erstellte app\service\Mailer Instanz verwendet.

Es ist zu beachten, dass in config/dependence.php new verwendet wird, um die Klasse Mailer zu instanziieren. Dies ist in diesem Beispiel unproblematisch, aber stellen Sie sich vor, wenn die Mailer-Klasse von anderen Klassen abhängt oder die Mailer-Klasse interne Annotationsinjektionen verwendet, wird die Verwendung von new keine automatische Injektion ermöglichen. Eine Lösung besteht darin, benutzerdefinierte Schnittstelleninjektionen zu verwenden, um die Klasse über die Methoden Container::get(klassename) oder Container::make(klassename, [Konstruktorparameter]) zu initialisieren.

Benutzerdefinierte Schnittstelleninjektion

In realen Projekten wünschen wir uns in der Regel, interfacebasiert zu programmieren, anstatt konkrete Klassen zu verwenden. Zum Beispiel sollte in app\controller\UserController app\service\MailerInterface anstelle von app\service\Mailer importiert werden.

Definieren Sie das MailerInterface.

<?php
namespace app\service;

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

Definieren Sie die Implementierung des 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)
    {
        // Der Code zum Senden von E-Mails ist hier weggelassen
    }
}

Importieren Sie das MailerInterface anstelle der konkreten Implementierung.

<?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 und willkommen!');
        return response('ok');
    }
}

In config/dependence.php wird das MailerInterface wie folgt definiert.

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

So wird bei Bedarf die Implementierung von Mailer automatisch verwendet, wenn die Verwendung des MailerInterface erforderlich ist.

Der Vorteil der Schnittstellenprogrammierung besteht darin, dass wir beim Ersetzen einer Komponente den Anwendungscode nicht ändern müssen; wir müssen nur die konkrete Implementierung in config/dependence.php ändern. Dies ist auch bei der Durchführung von Unit-Tests äußerst nützlich.

Weitere benutzerdefinierte Injektionen

In config/dependence.php können nicht nur Klassenabhängigkeiten definiert werden, sondern auch andere Werte wie Strings, Zahlen, Arrays usw.

Zum Beispiel wird config/dependence.php wie folgt definiert:

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

In diesem Fall können wir @Inject verwenden, um smtp_host und smtp_port in die Eigenschaften der Klasse zu injizieren.

<?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)
    {
        // Der Code zum Senden von E-Mails ist hier weggelassen
        echo "{$this->smtpHost}:{$this->smtpPort}\n"; // Gibt 192.168.1.11:25 aus
    }
}

Hinweis: In @Inject("key") sind die Anführungszeichen wichtig

Weitere Inhalte

Bitte beachten Sie das php-di Handbuch