1. Code
  2. Coding Fundamentals
  3. Design Patterns

Примеры внедрения зависимостей в PHP с помощью компонентов Symfony

Scroll to top

Russian (Pусский) translation by Alexey Pyltsyn (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!

Поделитесь своими мыслями и предложениями, используя канал ниже. Мне хотелось бы обсудить эту статью с вами!