Внедрение зависимостей/Расположение сервисов
Перед чтением этого раздела рекомендуется ознакомиться с разделом, объясняющим, почему Phalcon использует расположение сервисов и внедрение зависимостей.
Phalcon\Di — это компонент, реализующий внедрение зависимостей и расположение сервисов, и он сам является контейнером для них.
Поскольку Phalcon сильно декомпозирован, Phalcon\Di необходим для интеграции различных компонентов фреймворка. Разработчик также может использовать этот компонент для внедрения зависимостей и управления глобальными экземплярами различных классов, используемых в приложении.
В основном, этот компонент реализует шаблон обращения управления. Применяя его, объекты не получают свои зависимости с помощью сетеров или конструкторов, а запрашивают инжектор зависимостей сервиса. Это уменьшает общую сложность, так как существует только один способ получения необходимых зависимостей внутри компонента.
Кроме того, этот шаблон повышает возможность тестирования кода, делая его менее подверженным ошибкам.
Регистрация сервисов в контейнере
Фреймворк сам или разработчик может регистрировать сервисы. Когда компонент A требует компонента B (или экземпляра его класса) для работы, он может запросить компонент B из контейнера, а не создавать новый экземпляр компонента B.
Этот способ работы предоставляет нам множество преимуществ:
- Мы можем легко заменить компонент на компонент, созданный нами или третьей стороной.
- Мы полностью контролируем инициализацию объекта, что позволяет нам устанавливать эти объекты по мере необходимости, прежде чем передавать их компонентам.
- Мы можем получить глобальные экземпляры компонентов структурированным и унифицированным способом.
Сервисы могут быть зарегистрированы с использованием нескольких типов определений:
Простая регистрация
Как видно ранее, существуют несколько способов регистрации сервисов. Мы называем их простыми:
Строка
Этот тип ожидает имя допустимого класса, возвращая объект указанного класса. Если класс не загружен, он будет создан с использованием автозагрузчика. Этот тип определения не позволяет указать аргументы для конструктора класса или параметры:
// Return new Phalcon\Http\Request();
$di->set(
"request",
"Phalcon\\Http\\Request"
);
Экземпляры классов
Этот тип ожидает объект. Поскольку объект не нужно разрешать, так как он уже является объектом, можно сказать, что это не совсем внедрение зависимостей, однако это полезно, если вы хотите заставить возвращаемую зависимость всегда быть одним и тем же объектом/значением:
use Phalcon\Http\Request;
// Return new Phalcon\Http\Request();
$di->set(
"request",
new Request()
);
Замыкания/Анонимные функции
Этот метод предлагает большую свободу в построении зависимости по желанию, однако изменить некоторые параметры внешне сложно, не изменяя полностью определение зависимости:
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
$di->set(
"db",
function () {
return new PdoMysql(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "blog",
]
);
}
);
Некоторые ограничения можно преодолеть, передав дополнительные переменные в среду замыкания:
use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
$config = new Config(
[
"host" => "127.0.0.1",
"username" => "user",
"password" => "pass",
"dbname" => "my_database",
]
);
// Using the $config variable in the current scope
$di->set(
"db",
function () use ($config) {
return new PdoMysql(
[
"host" => $config->host,
"username" => $config->username,
"password" => $config->password,
"dbname" => $config->name,
]
);
}
);
Вы также можете получить доступ к другим сервисам DI, используя метод %%%CODE_BLOCK_4%%:
use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
$di->set(
"config",
function () {
return new Config(
[
"host" => "127.0.0.1",
"username" => "user",
"password" => "pass",
"dbname" => "my_database",
]
);
}
);
// Using the 'config' service from the DI
$di->set(
"db",
function () {
$config = $this->get("config");
return new PdoMysql(
[
"host" => $config->host,
"username" => $config->username,
"password" => $config->password,
"dbname" => $config->name,
]
);
}
);
Сложная регистрация
Если необходимо изменить определение сервиса, не создавая/разрешая сервис, то мы должны определить сервисы с использованием синтаксиса массива. Определение сервиса с помощью определения массива может быть немного более громоздким:
use Phalcon\Logger\Adapter\File as LoggerFile;
// Register a service 'logger' with a class name and its parameters
$di->set(
"logger",
[
"className" => "Phalcon\\Logger\\Adapter\\File",
"arguments" => [
[
"type" => "parameter",
"value" => "../apps/logs/error.log",
]
]
]
);
// Using an anonymous function
$di->set(
"logger",
function () {
return new LoggerFile("../apps/logs/error.log");
}
);
Оба вышеприведенных определения сервиса дают одинаковый результат. Однако определение массива позволяет при необходимости изменять параметры сервиса:
// Change the service class name
$di->getService("logger")->setClassName("MyCustomLogger");
// Change the first parameter without instantiating the logger
$di->getService("logger")->setParameter(
0,
[
"type" => "parameter",
"value" => "../apps/logs/error.log",
]
);
Кроме того, используя синтаксис массива, вы можете использовать три типа внедрения зависимостей:
Внедрение через конструктор
Этот тип внедрения передает зависимости/аргументы в конструктор класса. Предположим, у нас есть следующий компонент:
namespace SomeApp;
use Phalcon\Http\Response;
class SomeComponent
{
/**
* @var Response
*/
protected $_response;
protected $_someFlag;
public function __construct(Response $response, $someFlag)
{
$this->_response = $response;
$this->_someFlag = $someFlag;
}
}
Сервис можно зарегистрировать следующим образом:
$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response"
]
);
$di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"arguments" => [
[
"type" => "service",
"name" => "response",
],
[
"type" => "parameter",
"value" => true,
],
]
]
);
Сервис «response» (Phalcon\Http\Response) разрешается для передачи в качестве первого аргумента конструктора, а второй — булевое значение (true), которое передаётся как есть.
Внедрение через сеттеры
Классы могут иметь сеттеры для внедрения необязательных зависимостей. Наш предыдущий класс можно изменить, чтобы он принимал зависимости с помощью сеттеров:
namespace SomeApp;
use Phalcon\Http\Response;
class SomeComponent
{
/**
* @var Response
*/
protected $_response;
protected $_someFlag;
public function setResponse(Response $response)
{
$this->_response = $response;
}
public function setFlag($someFlag)
{
$this->_someFlag = $someFlag;
}
}
Сервис с внедрением через сеттеры можно зарегистрировать следующим образом:
$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response",
]
);
$di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"calls" => [
[
"method" => "setResponse",
"arguments" => [
[
"type" => "service",
"name" => "response",
]
]
],
[
"method" => "setFlag",
"arguments" => [
[
"type" => "parameter",
"value" => true,
]
]
]
]
]
);
Внедрение через свойства
Менее распространённая стратегия заключается во внедрении зависимостей или параметров непосредственно в публичные атрибуты класса:
namespace SomeApp;
use Phalcon\Http\Response;
class SomeComponent
{
/**
* @var Response
*/
public $response;
public $someFlag;
}
Сервис с внедрением через свойства можно зарегистрировать следующим образом:
$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response",
]
);
$di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"properties" => [
[
"name" => "response",
"value" => [
"type" => "service",
"name" => "response",
],
],
[
"name" => "someFlag",
"value" => [
"type" => "parameter",
"value" => true,
],
]
]
]
);
Поддерживаемые типы параметров включают следующие:
| Тип | Описание | Пример |
|---|---|---|
| параметр | Представляет буквальное значение, которое должно быть передано в качестве параметра | ["type" => "parameter", "value" => 1234] |
| сервис | Представляет другой сервис в контейнере сервисов | ["type" => "service", "name" => "request"] |
| экземпляр | Представляет объект, который должен быть динамически создан | ["type" => "instance", "className" => "DateTime", "arguments" => ["now"]] |
Разрешение сервиса, определение которого является сложным, может быть немного медленнее, чем простые определения, представленные ранее. Однако они обеспечивают более надёжный подход к определению и внедрению сервисов.
Смешивание различных типов определений разрешено, каждый может решить, какой способ регистрации сервисов является наиболее подходящим в соответствии с потребностями приложения.
Синтаксис массива
Синтаксис массива также разрешен для регистрации сервисов:
use Phalcon\Di;
use Phalcon\Http\Request;
// Create the Dependency Injector Container
$di = new Di();
// By its class name
$di["request"] = "Phalcon\\Http\\Request";
// Using an anonymous function, the instance will be lazy loaded
$di["request"] = function () {
return new Request();
};
// Registering an instance directly
$di["request"] = new Request();
// Using an array definition
$di["request"] = [
"className" => "Phalcon\\Http\\Request",
];
В приведённых выше примерах, когда фреймворк нуждается в доступе к данным запроса, он запросит сервис с идентификатором «запрос» в контейнере. Контейнер, в свою очередь, вернёт экземпляр требуемого сервиса. Разработчик, в конечном итоге, может заменить компонент, когда ему это потребуется.
Каждый из методов (показанных в примерах выше), используемых для установки/регистрации сервиса, имеет свои преимущества и недостатки. Выбор того или иного метода зависит от разработчика и конкретных требований.
Установка сервиса с помощью строки проста, но лишена гибкости. Установка сервисов с использованием массива предоставляет гораздо большую гибкость, но делает код более сложным. Лямбда-функция — это хорошее равновесие между этими двумя вариантами, но может привести к большей необходимости в обслуживании, чем ожидалось.
Phalcon\Di предлагает отложенную загрузку для каждого хранящегося в нём сервиса. Если разработчик не решит напрямую создать объект и сохранить его в контейнере, любой объект, сохранённый в нём (через массив, строку и т. д.), будет загружаться в отложенном режиме, т. е. создаётся только при запросе.
Разрешение сервисов
Получение сервиса из контейнера сводится к простому вызову метода «get». Будет возвращён новый экземпляр сервиса:
$request = $di->get("request");
Или через магический метод:
$request = $di->getRequest();
Или с использованием синтаксиса доступа к массивам:
$request = $di["request"];
Аргументы могут быть переданы в конструктор путём добавления массива в качестве параметра к методу «get»:
// new MyComponent("some-parameter", "other")
$component = $di->get(
"MyComponent",
[
"some-parameter",
"other",
]
);
События
Phalcon\Di может отправлять события в EventsManager, если он присутствует. События вызываются с типом «di». Некоторые события при возвращении false могут остановить активную операцию. Поддерживаются следующие события:
| Название события | Вызывается | Может остановить операцию? | Вызывается на |
|---|---|---|---|
| beforeServiceResolve | Вызывается перед разрешением сервиса. Слушатели получают имя сервиса и параметры, передаваемые ему. | Нет | Слушатели |
| afterServiceResolve | Вызывается после разрешения сервиса. Слушатели получают имя сервиса, экземпляр и параметры, передаваемые ему. | Нет | Слушатели |
Общие сервисы
Сервисы могут быть зарегистрированы как «общие», что означает, что они всегда будут действовать как синглтоны. После первого разрешения сервиса, тот же экземпляр возвращается всякий раз, когда потребитель получает сервис из контейнера:
use Phalcon\Session\Adapter\Files as SessionFiles;
// Register the session service as "always shared"
$di->setShared(
"session",
function () {
$session = new SessionFiles();
$session->start();
return $session;
}
);
// Locates the service for the first time
$session = $di->get("session");
// Returns the first instantiated object
$session = $di->getSession();
Альтернативный способ регистрации общих сервисов — передать «true» в качестве третьего параметра метода «set»:
// Register the session service as "always shared"
$di->set(
"session",
function () {
// ...
},
true
);
Если сервис не зарегистрирован как общий, и вы хотите быть уверены, что к общему экземпляру будет обращаться каждый раз, когда сервис получает из DI, вы можете использовать метод «getShared»:
$request = $di->getShared("request");
Работа с сервисами по отдельности
После регистрации сервиса в контейнере сервисов вы можете получить к нему доступ для индивидуальной обработки:
use Phalcon\Http\Request;
// Register the "request" service
$di->set("request", "Phalcon\\Http\\Request");
// Get the service
$requestService = $di->getService("request");
// Change its definition
$requestService->setDefinition(
function () {
return new Request();
}
);
// Change it to shared
$requestService->setShared(true);
// Resolve the service (return a Phalcon\Http\Request instance)
$request = $requestService->resolve();
Создание экземпляров классов через контейнер сервисов
При запросе сервиса в контейнере сервисов, если он не находит сервис с тем же именем, он попытается загрузить класс с тем же именем. С таким поведением мы можем заменить любой класс другим, просто зарегистрировав сервис с его именем:
// Register a controller as a service
$di->set(
"IndexController",
function () {
$component = new Component();
return $component;
},
true
);
// Register a controller as a service
$di->set(
"MyOtherComponent",
function () {
// Actually returns another component
$component = new AnotherComponent();
return $component;
}
);
// Create an instance via the service container
$myComponent = $di->get("MyOtherComponent");
Вы можете воспользоваться этим, всегда создавая экземпляры своих классов через контейнер сервисов (даже если они не зарегистрированы как сервисы). DI будет использовать допустимый автозагрузчик для окончательной загрузки класса. Таким образом, вы можете легко заменить любой класс в будущем, реализовав определение для него.
Автоматическое внедрение самого DI
Если класс или компонент требует самого DI для поиска сервисов, DI может автоматически внедрять себя в создаваемые экземпляры. Для этого необходимо реализовать Phalcon\Di\InjectionAwareInterface в ваших классах:
use Phalcon\DiInterface;
use Phalcon\Di\InjectionAwareInterface;
class MyClass implements InjectionAwareInterface
{
/**
* @var DiInterface
*/
protected $_di;
public function setDi(DiInterface $di)
{
$this->_di = $di;
}
public function getDi()
{
return $this->_di;
}
}
Затем, после разрешения сервиса, $di будет передаваться в setDi() автоматически:
// Register the service
$di->set("myClass", "MyClass");
// Resolve the service (NOTE: $myClass->setDi($di) is automatically called)
$myClass = $di->get("myClass");
Организация сервисов в файлах
Вы можете улучшить организацию приложения, перемещая регистрацию сервисов в отдельные файлы вместо выполнения всего в загрузчике приложения:
$di->set(
"router",
function () {
return include "../app/config/routes.php";
}
);
Затем в файле (”../app/config/routes.php”) возвращается результирующий объект:
$router = new MyRouter();
$router->post("/login");
return $router;
Статический доступ к DI
При необходимости можно получить доступ к последнему созданному DI в статической функции следующим образом:
use Phalcon\Di;
class SomeComponent
{
public static function someMethod()
{
// Get the session service
$session = Di::getDefault()->getSession();
}
}
Фабричный по умолчанию DI
Несмотря на то, что декомпозиция Phalcon предоставляет нам большую свободу и гибкость, возможно, нам просто нужно использовать его как полнофункциональный фреймворк. Для этого фреймворк предоставляет вариант Phalcon\Di, называемый Phalcon\Di\FactoryDefault. Этот класс автоматически регистрирует соответствующие службы, входящие в состав фреймворка, чтобы действовать как полнофункциональный.
use Phalcon\Di\FactoryDefault; $di = new FactoryDefault();
Соглашения об именах служб
Хотя вы можете регистрировать службы с нужными вам именами, Phalcon имеет несколько соглашений об именах, которые позволяют ему получать правильную (встроенную) службу, когда это необходимо.
| Имя службы | Описание | Значение по умолчанию | Разделяемая |
|---|---|---|---|
| dispatcher | Служба диспетчеризации контроллеров | Phalcon\Mvc\Dispatcher | Да |
| router | Служба маршрутизации | Phalcon\Mvc\Router | Да |
| url | Служба генератора URL | Phalcon\Mvc\Url | Да |
| request | Служба среды HTTP-запроса | Phalcon\Http\Request | Да |
| response | Служба среды HTTP-ответа | Phalcon\Http\Response | Да |
| cookies | Служба управления HTTP-cookies | Phalcon\Http\Response\Cookies | Да |
| filter | Служба фильтрации входных данных | Phalcon\Filter | Да |
| flash | Служба сообщений Flash | Phalcon\Flash\Direct | Да |
| flashSession | Служба сообщений Flash для сессий | Phalcon\Flash\Session | Да |
| session | Служба сессий | Phalcon\Session\Adapter\Files | Да |
| eventsManager | Служба управления событиями | Phalcon\Events\Manager | Да |
| db | Служба низкоуровневого подключения к базе данных | Phalcon\Db | Да |
| security | Справочные материалы по безопасности | Phalcon\Security | Да |
| crypt | Шифрование/дешифрование данных | Phalcon\Crypt | Да |
| tag | Справочные материалы по генерации HTML | Phalcon\Tag | Да |
| escaper | Контекстуальное экранирование | Phalcon\Escaper | Да |
| annotations | Парсер аннотаций | Phalcon\Annotations\Adapter\Memory | Да |
| modelsManager | Служба управления моделями | Phalcon\Mvc\Model\Manager | Да |
| modelsMetadata | Служба метаданных моделей | Phalcon\Mvc\Model\MetaData\Memory | Да |
| transactionManager | Служба менеджера транзакций моделей | Phalcon\Mvc\Model\Transaction\Manager | Да |
| modelsCache | Кэш-бекенд для кэша моделей | Нет | Нет |
| viewsCache | Кэш-бекенд для фрагментов представлений | Нет | Нет |
Реализация собственного DI
Для создания собственного DI, заменяющего тот, который предоставляет Phalcon, или расширяющего текущий, необходимо реализовать интерфейс Phalcon\DiInterface.
© 2011–2017 Phalcon Framework Team
Licensed under the Creative Commons Attribution License 3.0.
https://docs.phalconphp.com/en/latest/reference/di.html