Примеры внедрения зависимостей в PHP с помощью компонентов Symfony
() translation by (you can also view the original English article)
В этой статье мы рассмотрим некоторые примеры с использованием компонента Symfony DependencyInjection. Вы узнаете основы внедрения зависимостей, который позволяет писать более чистый и модульный код, и вы увидите, как использовать его в вашем PHP-приложение с использованием компонента Symfony.
Что такое Symfony-компонент DependencyInjection?
Компонент Symfony DependencyInjection предоставляет стандартный способ для создания объектов и обработки управления зависимостями в PHP-приложениях. Сердце компонента DependencyInjection является контейнер, который содержит все доступные сервисы в приложении.
Во время этапа начальной загрузки вашего приложения вы должны зарегистрировать все сервисы вашего приложения в контейнер. На более позднем этапе контейнер отвечает за создание запрашиваемых сервисов. Что еще более важно, так это то, что контейнер отвечает также за создание и внедрение зависимости сервисов.
Преимущество этого подхода заключается в том, что вам не нужно жестко задавать в коде процесс создания экземпляров объектов, поскольку зависимости будут обнаружены и создаваться и внедряться автоматически. Это создает слабую связанность между частями приложения.
В этой статье мы будем изучать, как вы можете раскрыть потенциал компонента DependencyInjection. Как обычно мы начнем с инструкции по установке и конфигурации, а затем реализуем несколько реалистичных примеров использования, чтобы продемонстрировать ключевые концепции.
Установка и настройка
В этом разделе мы сначала установим компонент DependencyInjection. Я предполагаю, что вы уже установили Composer в вашу систему, поскольку он нам понадобится для установки компонента DependencyInjection, доступного в Packagist.
Итак, идем дальше и устанавливаем компонент DependencyInjection, используя следующую команду.
1 |
$composer require symfony/dependency-injection
|
Эта команда создаст файл composer.json, содержимое которого должно быть таким:
1 |
{
|
2 |
"require": { |
3 |
"symfony/dependency-injection": "^4.1", |
4 |
}
|
5 |
}
|
Мы также установим несколько компонентов, которые будут использоваться в наших примерах.
Если вы хотите загрузить сервисы из YAML-файла вместо его определения в PHP-коде, то для этого нужен компонент Yaml, поскольку он помогает вам конвертировать YAML-строки в PHP-совместимые типы данных и наоборот.
1 |
$composer require symfony/yaml
|
Наконец, мы установим в компонент Config, который предоставляет несколько служебных классов для инициализации и обработки значений конфигурации, которые определены в различных типах файлов, таких как YAML, INI и XML. В нашем случае мы будем использовать его для загрузки сервисов из YAML-файла.
1 |
$composer require symfony/config
|
Изменим файл composer.json, чтобы он выглядеть как показано ниже.
1 |
{
|
2 |
"require": { |
3 |
"symfony/dependency-injection": "^4.1", |
4 |
"symfony/config": "^4.1", |
5 |
"symfony/yaml": "^4.1" |
6 |
},
|
7 |
"autoload": { |
8 |
"psr-4": { |
9 |
"Services\\": "src" |
10 |
},
|
11 |
"classmap": ["src"] |
12 |
}
|
13 |
}
|
Поскольку мы добавили новое поле classmap, давайте обновим автозагрузчик Composer, выполнив следующую команду.
1 |
$composer dump -o |
Теперь вы можете использовать пространство имен Services
для автоматической загрузки классов в каталоге src.
Итак, это была часть установки, но как вы предполагаете использовать этот компонент? На самом деле нужно просто включить файл autoload.php, созданный Composer в вашем приложении, как показано в следующем фрагменте.
1 |
<?php
|
2 |
require_once './vendor/autoload.php'; |
3 |
|
4 |
// application code
|
5 |
?>
|
Как работать с Container
В этом разделе мы рассмотрим пример для демонстрации, как вы могли бы внедрить сервисы в контейнер. Контейнер должен выступать в качестве центрального хранилища, который содержит все услуги вашего приложения. Далее, мы могли бы использовать контейнер для получения услуг по мере необходимости.
Для начала, давайте определим довольно простой сервис src/DemoService.php со следующим содержимым.
1 |
<?php
|
2 |
// src/DemoService.php
|
3 |
namespace Services; |
4 |
|
5 |
class DemoService |
6 |
{
|
7 |
public function helloWorld() |
8 |
{
|
9 |
return "Hello World!\n"; |
10 |
}
|
11 |
}
|
Это очень простой сервис, который просто реализует метод helloWorld
на данный момент.
Далее теперь создадим файл basic_container.php со следующим содержимым в корневом каталоге вашего приложения.
1 |
<?php
|
2 |
// basic_container.php
|
3 |
require_once './vendor/autoload.php'; |
4 |
use Symfony\Component\DependencyInjection\ContainerBuilder; |
5 |
|
6 |
// init service container
|
7 |
$containerBuilder = new ContainerBuilder(); |
8 |
|
9 |
// add service into the service container
|
10 |
$containerBuilder->register('demo.service', '\Services\DemoService'); |
11 |
|
12 |
// fetch service from the service container
|
13 |
$demoService = $containerBuilder->get('demo.service'); |
14 |
echo $demoService->helloWorld(); |
Для начала мы создали объект ContainerBuilder
с помощью конструктора new ContainerBuilder()
. Затем мы использовали метод register
объекта ContainerBuilder
для внедрения нашего пользовательского сервиса \Services\DemoService
в контейнер. demo.service
действует как псевдоним для нашего сервиса.
И наконец, мы использовали метод get
в объекте ContainerBuilder
для получения нашего сервиса из контейнера и использовали его для вызова метода helloWorld
.
Итак, сейчас была показана простое использование работы с контейнером. В следующем разделе, мы расширим этот пример, чтобы выяснить, как разрешены зависимости класса, используя контейнер.
Реальный пример
В этом разделе мы напишем пример, демонстрирующий, как разрешаются зависимости класса с помощью компонента DependencyInjection.
Чтобы продемонстрировать это, мы создадим новый сервис DependentService
, который требует сервис DemoService
, созданный в предыдущем разделе, в качестве зависимости. Таким образом, мы увидим, как сервис DemoService
автоматически внедряется в качестве зависимости при создании экземпляра сервиса DependentService
.
Идем дальше и создаем файл src/DependentService.php со следующим содержимым для определения сервиса, DependentService
.
1 |
<?php
|
2 |
// src/DependentService.php
|
3 |
namespace Services; |
4 |
|
5 |
class DependentService |
6 |
{
|
7 |
private $demo_service; |
8 |
|
9 |
public function __construct(\Services\DemoService $demoService) |
10 |
{
|
11 |
$this->demo_service = $demoService; |
12 |
}
|
13 |
|
14 |
public function helloWorld() |
15 |
{
|
16 |
return $this->demo_service->helloWorld(); |
17 |
}
|
18 |
}
|
Как вы можете видеть, сервис \Services\DemoService
требует для своей инициализации сервис DependentService
.
Не останавливаемся и создаем файл di_container.php с приведенным ниже содержимым.
1 |
<?php
|
2 |
// di_container.php
|
3 |
require_once './vendor/autoload.php'; |
4 |
use Symfony\Component\DependencyInjection\ContainerBuilder; |
5 |
use Symfony\Component\DependencyInjection\Reference; |
6 |
|
7 |
// init service container
|
8 |
$containerBuilder = new ContainerBuilder(); |
9 |
|
10 |
// add demo service into the service container
|
11 |
$containerBuilder->register('demo.service', '\Services\DemoService'); |
12 |
|
13 |
// add dependent service into the service container
|
14 |
$containerBuilder->register('dependent.service', '\Services\DependentService') |
15 |
->addArgument(new Reference('demo.service')); |
16 |
|
17 |
// fetch service from the service container
|
18 |
$dependentService = $containerBuilder->get('dependent.service'); |
19 |
echo $dependentService->helloWorld(); |
Мы используем один и тот же метод register
для внедрения нашего пользовательского сервиса \Services\DependentService
в контейнер.
В дополнение к этому, мы также использовали метод addArgument
, чтобы указать контейнеру о зависимости сервиса DependentService
. Мы использовали класс Reference
, чтобы сообщить контейнеру, что ему нужно внедрить сервис demo.service
при инициализации сервиса dependent.service
. Таким образом, зависимость автоматически внедряется по мере необходимости!
И последнее, мы использовали метод get
объекта ContainerBuilder
для извлечения сервиса dependent.service
из объекта ContainerBuilder
и использовали его для вызова метода helloWorld
.
Таким путем компонент DependencyInjection обеспечивает стандартный способ для создания экземпляров объектов и внедрения зависимостей в ваше приложение.
Как динамически загружать сервисы, используя YAML-файл
В этом последнем разделе мы изучим, как можно динамически загрузить сервисы из YAML-файла. В принципе, мы будем обновлять пример, рассмотренный в предыдущем разделе.
Наряду с компонентом DependencyInjection нам также понадобится еще два Symfony-компонента для реализации примера — Config и Yaml. Напоминаем, что мы уже установили эти два компонента в разделе "Установка и настройка" вместе с компонентом DependencyInjection. Поэтому мы готовы начать!
Продолжим дальше и создадим файл services.yaml со следующим содержимым в корневом каталоге вашего приложения.
1 |
services: |
2 |
demo.service: |
3 |
class: \Services\DemoService |
4 |
dependent.service: |
5 |
class: \Services\DependentService |
6 |
arguments: ["@demo.service"] |
Как вы можете видеть, довольно просто определить сервисы, используя синтаксис YAML. Для определения зависимостей сервисов, вам нужно будет использовать ключ arguments
.
Далее создаем файл di_yaml_container.php со следующим содержимым.
1 |
<?php
|
2 |
// di_yaml_container.php
|
3 |
require_once './vendor/autoload.php'; |
4 |
use Symfony\Component\DependencyInjection\ContainerBuilder; |
5 |
use Symfony\Component\Config\FileLocator; |
6 |
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; |
7 |
|
8 |
// init service container
|
9 |
$containerBuilder = new ContainerBuilder(); |
10 |
|
11 |
// init yaml file loader
|
12 |
$loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__)); |
13 |
|
14 |
// load services from the yaml file
|
15 |
$loader->load('services.yaml'); |
16 |
|
17 |
// fetch service from the service container
|
18 |
$serviceOne = $containerBuilder->get('dependent.service'); |
19 |
echo $serviceOne->helloWorld(); |
Все почти то же самое, за исключением того, что мы загружаем сервисы из services.yaml, а не определяем их в самом коде PHP. Это позволяет динамически определять зависимости приложения.
Заключение
Компонент Symfony DependencyInjection занял центральное место в этой обучающей статье. Мы увидели, как установить и настроить DependencyInjection, а также некоторые реальные примеры того, как его использовать.
Я действительно очарован и рад несвязанным друг с другом компонентам фреймворка Symfony, которые вы можете просто найти и использовать в вашем приложении. Включите их к ваш код, и они просто работают! В целом, я могу только увидеть преимущества этого нового подхода фреймворка для нашего сообщества PHP!
Поделитесь своими мыслями и предложениями, используя канал ниже. Мне хотелось бы обсудить эту статью с вами!