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 specificaPSR-11
. Se non desideri utilizzarephp-di
, puoi qui creare e restituire un'altra istanza di un contenitore conforme aPSR-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 ophp-di
possono completare l'iniezione automatica delle dipendenze; le istanze create manualmente connew
non possono completare l'iniezione delle dipendenze. Se necessiti dell'iniezione, devi utilizzare l'interfacciasupport\Container
in sostituzione della dichiarazionenew
, 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