Автоматическая инъекция зависимостей
В webman автоматическая инъекция зависимостей является необязательной функцией, которая по умолчанию отключена. Если вам нужна автоматическая инъекция зависимостей, рекомендуется использовать php-di. Ниже приведены способы интеграции webman с php-di
.
Установка
composer require psr/container ^1.1.1 php-di/php-di ^6.3 doctrine/annotations ^1.14
Измените конфигурацию config/container.php
, итоговое содержимое будет следующим:
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();
В файле
config/container.php
в конечном итоге возвращается экземпляр контейнера, соответствующий стандартуPSR-11
. Если вы не хотите использоватьphp-di
, вы можете создать и вернуть другой экземпляр контейнера, соответствующий стандартуPSR-11
.
Инъекция через конструктор
Создайте app/service/Mailer.php
(если каталог не существует, создайте его самостоятельно) со следующим содержимым:
<?php
namespace app\service;
class Mailer
{
public function mail($email, $content)
{
// Код отправки почты опущен
}
}
Содержимое app/controller/UserController.php
будет следующим:
<?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');
}
}
В нормальных условиях, для создания экземпляра app\controller\UserController
, потребуется следующий код:
$mailer = new Mailer;
$user = new UserController($mailer);
При использовании php-di
разработчику не нужно вручную создавать экземпляр Mailer
в контроллере, webman автоматически позаботится об этом. Если в процессе создания Mailer
есть зависимости от других классов, webman также автоматически создаст и инъектирует их. Разработчику не требуется никаких дополнительных инициализаций.
Внимание
Только экземпляры, созданные фреймворком илиphp-di
, могут пройти автоматическую инъекцию зависимостей. Экземпляры, созданные вручную с помощьюnew
, не смогут пройти автоматическую инъекцию зависимостей. Если требуется инъекция, используйте интерфейсsupport\Container
вместо выраженияnew
, например:
use app\service\UserService;
use app\service\LogService;
use support\Container;
// Экземпляры, созданные с помощью ключевого слова new, не могут пройти инъекцию зависимостей
$user_service = new UserService;
// Экземпляры, созданные с помощью ключевого слова new, не могут пройти инъекцию зависимостей
$log_service = new LogService($path, $name);
// Экземпляры, созданные с помощью Container, могут пройти инъекцию зависимостей
$user_service = Container::get(UserService::class);
// Экземпляры, созданные с помощью Container, могут пройти инъекцию зависимостей
$log_service = Container::make(LogService::class, [$path, $name]);
Инъекция через аннотации
Кроме автоматической инъекции зависимостей через конструктор, мы также можем использовать аннотации для инъекции. Продолжая предыдущий пример, изменим app\controller\UserController
следующим образом:
<?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');
}
}
Этот пример использует аннотацию @Inject
для инъекции и аннотацию @var
для указания типа объекта. Этот пример аналогичен инъекции через конструктор, но код более лаконичен.
Внимание
webman до версии 1.4.6 не поддерживает инъекцию параметров контроллера, например следующий код не поддерживается в webman <= 1.4.6
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
// Инъекция параметров контроллера до версии 1.4.6 не поддерживается
public function register(Request $request, Mailer $mailer)
{
$mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
Пользовательская инъекция конструктора
Иногда параметры, передаваемые в конструктор, могут быть не экземплярами классов, а строками, числами, массивами и т. д. Например, конструктор Mailer может потребовать передачи ip-адреса и порта 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)
{
// Код отправки почты опущен
}
}
В этом случае нельзя напрямую использовать ранее описанную автоматическую инъекцию конструктора, так как php-di
не может определить, каковы значения $smtp_host
и $smtp_port
. В этом случае можно попробовать использовать пользовательскую инъекцию.
В config/dependence.php
(если файл не существует, создайте его самостоятельно) добавьте следующий код:
return [
// ... здесь опущены другие конфигурации
app\service\Mailer::class => new app\service\Mailer('192.168.1.11', 25);
];
Таким образом, когда потребуется получить экземпляр app\service\Mailer
для инъекции, будет автоматически использоваться созданный в этой конфигурации экземпляр app\service\Mailer
.
Мы заметили, что в файле config/dependence.php
для создания экземпляра Mailer используется new
, что в данном примере не является проблемой. Но представьте, если класс Mailer
зависел от других классов или использовал аннотационную инъекцию, использование инициализации через new
не позволит осуществить автоматическую инъекцию. Решение состоит в использовании пользовательского интерфейса инъекции через методы Container::get(имя класса)
или Container::make(имя класса, [параметры конструктора])
для инициализации классов.
Инъекция интерфейса по собственному проектированию
В реальных проектах мы предпочитаем программировать на основе интерфейсов, а не конкретных классов. Например, в app\controller\UserController
следует использовать app\service\MailerInterface
вместо app\service\Mailer
.
Определим интерфейс MailerInterface
.
<?php
namespace app\service;
interface MailerInterface
{
public function mail($email, $content);
}
Определим реализацию интерфейса 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)
{
// Код отправки почты опущен
}
}
Используйте интерфейс MailerInterface
, а не конкретную реализацию.
<?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');
}
}
В config/dependence.php
определите реализацию интерфейса MailerInterface
следующим образом.
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]);
}
];
Таким образом, когда функцию нужно использовать интерфейс MailerInterface
, автоматически будет применяться его реализация Mailer
.
Преимущество программирования на основе интерфейсов заключается в том, что, когда нам нужно заменить какой-либо компонент, не нужно изменять бизнес-код, достаточно изменить конкретную реализацию в файле
config/dependence.php
. Это также очень полезно при написании модульных тестов.
Другие пользовательские инъекции
В config/dependence.php
можно не только определять зависимости классов, но и задавать другие значения, такие как строки, числа, массивы и т. д.
Например, в config/dependence.php
можно определить следующее:
return [
'smtp_host' => '192.168.1.11',
'smtp_port' => 25
];
В этом случае мы можем использовать @Inject
для инъекции smtp_host
и smtp_port
в свойства класса.
<?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)
{
// Код отправки почты опущен
echo "{$this->smtpHost}:{$this->smtpPort}\n"; // Выводит 192.168.1.11:25
}
}
Внимание: в
@Inject("key")
используются двойные кавычки
Дополнительные материалы
Пожалуйста, ознакомьтесь с руководством php-di