PHP

Что такое Сервис-Контейнер? Фреймворк-независимое определение

Привет всем!

Если вы следите за моим блогом, я упоминал где-то в статье Decorator Design Pattern термин Service Container. В этой записи я постараюсь максимально просто объяснить, что такое сервисный контейнер и как он используется. Может быть, мы сможем создать собственное доказательство концепции сервисного контейнера

Но подождите! Что я имею в виду под фреймворком? Я просто хочу сказать, что попытаюсь определить, что такое сервисный контейнер, без использования фреймворков. Хотя я использую популярные фреймворки, такие как Laravel и Symfony, в реальной жизни (как будто это не часть реальной жизни), я хочу изучить и понять базовую концепцию, используя простые термины и реализацию. Короче просто фреймворко-независимое определение (FAD) :-) .

Сервис и контейнер

Начнем с того, что есть сервисы. Сервисы - это просто классы, которые мы создаем и используем в нашем коде. Это может быть класс репозитория, служебный класс, класс регистратора - все, что вы вводите в другие классы. Даже классы контроллеров можно считать сервисами. Контейнеры определены в словаре - это объект, который можно использовать для хранения чего-либо. Итак, сервисные контейнеры - это объекты, содержащие сервисы. :)

Внедрение зависимости

Вы, наверное, задались вопросом, зачем нам использовать сервисные контейнеры? В основном это для внедрения зависимостей. Внедрение зависимостей означает, что зависимости классов вводятся или передаются через методы конструктора или установщика.

Учиться на примере

Представьте себе этот пример, в котором классы имеют «вложенные» зависимости. Перед сервисными контейнерами и внедрением зависимостей я делал что-то вроде этого:

<?php

class UserEmailSender implements UserEmailSenderInterface
{
    private $repo;
    private $logger;
    public function __construct(UserRepositoryInterface $userRepo, LoggeerInterface $reader)
    {
        $this->repo = new UserRepository(new Connection());
        $this->logger = new Logger();
    }
    // Imagine more methods below
}
<?php

class UserImporter implements UserImporterInterface
{
    private $repo;
    private $csvReader;
    
    public function __construct(UserRepositoryInterface $userRepo, ReaderInterface $reader)
    {
        $this->repo = $userRepo;
        $this->csvReader = $reader;
    }
    // Imagine more methods below
}

Теперь каждый раз, когда мы хотим использовать UserImporter или UserEmailSender, нам нужно создать экземпляры их зависимостей. Каждый раз, всегда! Вроде этого:

<?php
//Actions/Users/Import.php
// Представьте себе этот код в вашем основном файле импорта PHP
$userRepository = new UserRepository(new Connection());
$csvReader = new CsvReader();

$userImporter = new UserImporter($userRepository, $csvReader);

// Пример испльзования
$userImporter->import($_POST['csv_file']);
<?php
//Actions/Users/SendMail.php
// Представьте себе этот код в вашем php-файле для отправки почты
$userRepository = new UserRepository(new Connection());
$logger = new Logger();

$userEmailSender = new UserEmailSender($userRepository, $logger);

// Пример использования:
$userEmailSender->send($_POST['user'], $_POST['body']);

Отстой, правда? Я делал такие вещи на всех своих уроках давным-давно. Но благодаря фреймворкам, предоставляющим сервис-контейнер, мы можем вывести логику создания экземпляров зависимостей из всех классов.

Идея контейнера служб заключается в том, что мы создаем или настраиваем все наши службы и помещаем их в контейнер служб для дальнейшего использования.

Сервисный контейнер Simple Array

Итак, чтобы увидеть, как работает сервисный контейнер, давайте попробуем создать свой собственный в простейшем виде. Мы собираемся использовать для этого интерфейс PSR-11 . В этом простом примере мы все настроим и создадим экземпляры наших сервисов в конструкторе. В других фреймворках, таких как Laravel, настройка выполняется в поставщиках услуг, и эти поставщики услуг затем используются контейнером служб, который знает службы, которые вы хотите настроить. В этом случае нашим поставщиком услуг будет простой массив ;-)

<?php

use Psr\Container\ContainerInterface;

class SimpleArrayServiceContainer implements ContainerInterface
{
    private $services = [];
    
    public function __construct() {
        $this->setupEverythingInArray();
    }
    public function get($id)
    {
        if(\isset($this->services[$id]) === false) {
            throw ServiceNotFoundException();
        }
        
        return $this->services[$id];
    }
    
    public function has($id)
    {
        return \isset($this->services[$id]);
    }
   
    private function setupEverythingInArray()
    {
        // Setup utiliity classes
        $this->services[LoggerInterface::class] = new Logger();
        
        $this->services[UserRepositoryInterface::class] = new UserRepository(new Connection()); 
        
        $this->services[ReaderInterface::class] = new CsvReader(); 
        // Setup application classes
        $this->services[UserImporterInterface::class] = new UserImporter($this->get(UserRepositoryInterface::class), $this->get(ReaderInterface::class)); 
        
        $this->services[UserEmailSenderInterface::class] = new UserImporter($this->get(UserRepositoryInterface::class), $this->get(LoggerInterface::class)); 
    }
}

Теперь, когда у нас есть сервисный контейнер, мы можем просто использовать его как единый источник истины, откуда будут поступать наши сервисы. Давайте посмотрим, как это будет выглядеть, используя его в наших классах действий.

Actions/Users/Import.php
<?php

// Imagine this code in your main import php file
$serviceContainer = new SimpleArrayServiceContainer();

$userImporter = $serviceContainer->get(UserImporterInterface::class);

// Just an example usage.
$userImporter->import($_POST['csv_file']);

Actions/Users/SendMail.php

<?php

// Imagine this code in your main send mail php file
$serviceContainer = new SimpleArrayServiceContainer();

$userEmailSender = $serviceContainer->get(UserEmailSenderInterface::class);

// Just an example usage.
$userEmailSender->send($_POST['user'], $_POST['body']);

Это еще не все

Наша реализация сервисного контейнера довольно проста и понятна, не так ли? Это простой пример того, как работает сервисный контейнер. Конечно, если вы знакомы с Laravel, у вас будет концепция автоматической привязки, которая просто использует Reflection и рекурсивно определяет, каковы зависимости каждого класса и многое другое.

Надеюсь, вам понравилось это «Framework-Agnostic Definition (FAD)» контейнера служб. Мы скоро рассмотрим и другие подобные вещи ;-)

Ура и счастливого кодирования! ;-)

 

 

https://unorderedlist.io/what-is-a-service-container/

PHP

Leave a reply