Контейнер зависимостей

Оглавление





Введение #
К оглавлению

Контейнер используется для так называемой инверсии зависимостей (IoC). Классы упаковываются в контейнер под алиасами (идентификаторами). По этим идентификаторам можно получить объект класса, при этом сам класс можно безболезненно подменить на другой, заменив его только в контейнере, не переписывая код во всей системе, там, где он может понадобиться.

Это помогает не только гибко организовать код, используя различные сторонние библиотеки, но и организовать так называемую "слоистую архитектуру", когда доменная модель не привязана к инфраструктуре. Модель не использует вызовы сервисов инфраструктуры напрямую, это позволит при сменене фреймворка заменить только контейнер и регистрацию сервисов в нем.

ABC-фреймворк предоставляет собственный контейнер для внедрения зависимостей, основанный на анонимных функциях, что дает возможность простой предварительной настройки классов.

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

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

Получить экземпляр контейнера можно, запросив обычный сервис:
1
2
3

   $container 
ABC::newService(ABC::CONTAINER); 




Сервисы и зависимости упаковываются в замыкания. Это дает возможность предварительной настройки.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// Простая упаковка
    
$callable = function() {
                   return new \
ABC\App\Example
               };
               
// Так можно предварительно настроить зависимость 
    
$callable = function() use ($paramRorConstruct$config) {
                   
$example = new \ABC\App\Example($paramRorConstruct);
                   
$example->setConfig($config);
                   return 
$example;
               }; 

// Если предварительная настройка не нужна, можно просто использовать имя класса
// Контейнер запакует его автоматически
    
$container->add('Example', \ABC\App\Example::class);
 




Сервис контейнера предоставляет следующие публичные методы #
К оглавлению


1 setMaps() Устанавливает карты зависимостей
2 typeConstruct() Устанавливает тип внедрения через конструктор
3 typeMethodCall() Устанавливает тип внедрения через сеттеры
4 typePropertyes() Устанавливает тип внедрения через свойства
5 add() Регистрирует сервисы в контейнере
6 addGlobal() Регистрирует в контейнере сервисы с общим доступом
7 factory() Обертка для фабричных сервисов
8 addDependence() Устанавливает зависимости для указанного сервиса
9 extendsServices() Наследует зависимости у указанных сервисов
10 addDafault() Устанавливает общие зависимости
11 clearDafault() Очищает все ранее установленные дефолтные блоки
12 get() Инициализирует и возвращает объект зависимости
13 getNew() Инициализирует и возвращает новый объект зависимости
14 has() Проверяет, есть ли сервис в контейнере
15 createLocator() Создает внутренний сервис-локатор
16 bind() Привязывает к замыканию область видимости
17 serviceSynthetic() Объявляет сервис синтетическим
18 isSynthetic() Проверяет, синтетический сервис или нет
19 unsetObject() Удаляет объект сервиса из хранилища




setMaps() public method
К списку методов

Устанавливает карты зависимостей.

Этот метод настраивает контейнер по картам зависимостей. Принимает аргументами карты в виде массивов. Смотри "карта зависимостей"

Смотри еще add(), addGlobal().

public Container::setMaps ( $localMap [], $globalMap [] )
$localMap array Карта сервисов и зависимостей, которые будут установлены локально, аналогично методу add().
$globalMap array Карта сервисов и зависимостей, которые будут установлены с общим доступом из всех инициализированных контейнеров по приципу метода addGlobal()
return this




typeConstruct() public method
К списку методов

Устанавливает тип внедрения зависимостей через конструктор

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

public Container::typeConstruct ( ...$services )
$source array|boolean Массив, в котором указаны сервисы, для которых выбран тип внедрения через сеттеры. Можно указать сервисы не массивом, а аргументами.
Если передать true, этот тип будет установлен для всех сервисов.
return this




typeMethodCall() public method
К списку методов

Устанавливает тип внедрения зависимостей через сеттеры

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

public Container::typeMethodCall ( ...$services )
$source array|boolean Массив, в котором указаны сервисы, для которых выбран тип внедрения через сеттеры. Можно указать сервисы не массивом, а аргументами.
Если передать true, этот тип будет установлен для всех сервисов.
return this




typePropertyes() public method
К списку методов

Устанавливает тип внедрения зависимостей через публичные свойства

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

public Container::typePropertyes ( ...$services )
$source array|boolean Массив, в котором указаны сервисы, для которых выбран тип внедрения через сеттеры. Можно указать сервисы не массивом, а аргументами.
Если передать true, этот тип будет установлен для всех сервисов.
return this




add() public method
К списку методов

Помещает сервис в контейнер.

Этот метод записывает в хранилище анонимную функцию под уникальным идентификатором. В теле функции происходит инициализация класса сервиса.
Если передать вторым аргументом имя класса, он будет упакован в замыкание автоматически.
Смотри пример

Смотри еще setMaps(), addGlobal().

public Container::add ( $service, $source = null )
$source string|string Идентификатор сервиса, либо карта зависимостй. Сервисы будут установлены локально.
$source callable|string|object Замыкание, в котором упакована инициализация объекта, либо имя класса, либо объект сервиса.
return this




addGlobal() public method
К списку методов

Помещает в хранилище сервис с общим доступом

Этот метод записывает в хранилище анонимную функцию под уникальным идентификатором по принципу "синглтон". Этот сервис будет доступен во всех экземплярах контейнера.
Если передать вторым аргументом имя класса, он будет упакован в замыкание автоматически.

Смотри пример

Смотри еще setMaps(), add().

public Container::addGlobal ( $services, $source = null )
$source string|string Идентификатор сервиса, либо карта зависимостй. Сервисы будут установлены глобально, по принципу Singlton.
$source callable|string|object Замыкание, в котором упакована инициализация объекта, либо имя класса, либо объект сервиса.
return this




factory() public method
К списку методов

Обертка источника данных для сервиса

По умолчанию метод get() возвращает один и тот же инстанс класса (объект). Но бывают случаи, когда нужно возвращать всегда новый. И хотя есть метод getNew(), который это делает, можно задать эту опцию при регистрации сервисов. Нужно просто обернуть источник этим методом.

Смотри пример

public Container::factory ( $source, $source = null )
$source mix Источник данных для регистрируемого сервиса
return callable Замыкание, в котором упакован источник данных




addDependence() public method
К списку методов

Устанавливает зависимости, которые будут внедрены в сервис.

Выбирает по идентификатору из хранилища сервис и внедряет в него зависимости. Для каждого сервиса инициализируется новый объект зависимости, если она не зарегистрирована как сервис.

См. примеры

public Container::addDependence ( $serviceId, $dependences )
$serviceId string Сервис-реципиент, в который будет помещены зависимости.
$dependences array Массив, состоящий из пар property => dependence. Смотри примеры.
return this




extendsServices() public method
К списку методов

Наследует зависимости у указанных сервисов.

Чтобы избежать повторов кода, можно унаследовать ранее установленные зависимости. Для этого нужно задать параметрами те сервисы, зависимости которых требуются в устанавливаемом сервисе. Метод можно вызвать только после установки сервиса методами add() и addGlobal()
Если сервисы не наследуются на стороне PHP, то в реципиенте должны быть реализованы сеттеры для наследуемых зависимостей.
Зависимости, устанавливаемые для сервиса методом addDependence() перезапишут унаследованные этим методом.

См. примеры

public Container::extendsServices ( ...$services)
$services string Сервисы, зависимости которых будут внедрены в текущий сервис.
return this




addDafault() public method
К списку методов

Устанавливает общие зависимости для всех сервисов, помещенных в контейнер.

Зависимости, установленные этим методом автоматически будут применены ко всем сервисам контейнера. Зависимости, устанавливаемые для отдельного сервиса методом addDependence() перезапишут дефолтные. Смотри пример

public Container::addDafault ( $dependances )
$dependances array Массив зависимостей в виде пар property => callable. Смотри пример
return this




clearDafault() public method
К списку методов

Очищает все ранее установленные дефолтные блоки.

Этим методом можно удалить все общие зависимости, установленные раньше. Дальше можно установить новый дефолтный блок. Смотри пример

public Container::clearDafault ( )
return this




get() public method
К списку методов

Инициализирует и возвращает объект зависимости.

Находит по идентификатору и возвращает объект зависимости. Если сервис был упакован в контейнер, как shared, то возвращается один и тот же объект по принципу "синглтон". Если он упакован обычно, то каждый раз возвращается новый объект.

Смотри еще getNew().

public Container::get ( $serviceId )
$serviceId string Идентификатор сервиса в контейнере.
return object Объект сeрвиса




getNew() public method
К списку методов

Инициализирует и возвращает новый объект зависимости.

Возвращает по идентификатору сервиса новый объект, вне зависимости от того, как он был упакован.

Смотри еще get().

public Container::getNew ( $serviceId )
$serviceId string Идентификатор сервиса в контейнере.
return object Объект сeрвиса




has() public method
К списку методов

Проверяет, есть ли сервис в контейнере.

Возвращает булево значение в зависимости от того, есть ли в контейнере сервис с таким идентификатором.

public Container::has ( $serviceId )
$serviceId string Идентификатор зависимости в контейнере.
return boolean true, если есть, false, если нет.




createLocator() public method
К списку методов

Устанавливает внутренний сервис-локатор.

Локализует указанные сервисы в отдельный контейнер, который можно использовать как зависимость.

public Container::createLocator ( $locatorId, ...$services )
$locatorId string Идентификатор локатора в контейнере.
$services array|string Массив карты зависимостей, либо перечень идентификаторов сервисов, которые будут локализованы в локаторе
return object this




bind() public method
К списку методов

Присваивает замыканию новую область видимости.

По умолчанию область видимости всех замыканий контейнера, это объект самого контейнера. Этот метод позволяет присвоить новую область.

public Container::bind ( $callable, $newthis )
$callable callable Замыкание, которому присваивается новая область видимости
$newthis object Новая область видимости
return callable Продублированное замыкание с новой областью видимости




serviceSynthetic() public method
К списку методов

Объявляет сервис синтетическим.

Если объявить сервис синтетическим, в него будет запрещено внедрение зависимости.

public Container::serviceSynthetic ( $serviceId )
$serviceId string Идентификатор зависимости в контейнере.
return void




isSynthetic() public method
К списку методов

Проверяет, объявлен ли сервис синтетическим.

Если сервис объявлен синтетическим, в него запрещено внедрение зависимости. Проверить это помогает данный метод.

public Container::isSynthetic ( $serviceId )
$serviceId string Идентификатор зависимости в контейнере.
return boolean true, если сервис синтетический, false, если нет.




unsetObject() public method
К списку методов

Удаляет объект сервиса из хранилища.

Иногда нужно, что бы отработал деструктор сервиса. Но так, как объект хранится в контейнере, обычным unset() вызвать его не получится. Для этой цели и служит данный метод. При этом сервис остается в контейнере и может быть повторно вызван.

public Container::unsetObject ( $serviceId )
$serviceId string Идентификатор зависимости в контейнере.
return void




Примеры использования #
К оглавлению

Простая работа с сервисами #.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// Класс для проверки 
class MyClass 

    protected 
$time

    public function 
checkTime() 
    { 
        if (empty(
$this->time)) { 
            
$this->time microtime(true); 
        } 
         
        return 
$this->time
    } 




Регистрация сервисов в контейнере (используются разные способы).
1
2
3
4
5
6
7
8
9
10
11
12

    
// Помещаем класс MyClass в контейнер в виде анонимной функции 
    
$container->add('MyClass',  
                    function() { 
                        return new 
MyClass
                    } 
            );  
    
// То же самое, под другим идентификатором, используя имя класса        
    
$container->add('Other'MyClass::class);     
    
// Или можно сразу объект, если есть необходимость      
    
$container->add('Another', new MyClass);




Получение сервиса из контейнера методами get() и getNew() #

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

    
// Помещаем класс MyClass в контейнер, используя имя класса        
    
$container->add('MyClass'MyClass::class);     
    
    
// Так можно достать 
    
$obj $container->get('MyClass'); 
    echo 
$obj->checkTime();    // .....10.201  
    
echo '<br>'
    
sleep(1); 
    
// Метод get() всегда возвращает один и тот же объект
    // по принципу Singlton
    
$obj $container->get('MyClass'); 
    echo 
$obj->checkTime();    // .....10.201 
    
echo '<br>';     
    
    
// Это новый объект 
    
$obj $container->getNew('MyClass'); 
    echo 
$obj->checkTime(); // .....11.208
    
echo '<br>'




Регистрация фабричного сервиса. #

Чтобы метод get() всегда возвращал новый объект, можно зарегистрировать сервис, обернув источник данных методом factory() Источником могут служить любые данные как то: замыкание, имя класса, объект или любые произвольные данные.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

    
// Заворачиваем имя класса в фабричную обертуку
    
$container->add('MyClass'$container->factory(MyClass::class));

    
$obj $container->get('MyClass'); 
    echo 
$obj->checkTime();    // .....82.3049
    
    
echo '<br>'
    
sleep(1); 
    
// Теперь метод get() будет возвращать новые объекты
    // по принципу Singlton
    
$obj $container->get('MyClass'); 
    echo 
$obj->checkTime();    // .....82.3194 
    
echo '<br>';     




К оглавлению
Глобальные сервисы #.

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

Допустим в главной точке сборки приложения можно зарегистрировать глобальный сервис базы данных и больше не беспокоится о его наличии в других контейнерах.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

    
// Получаем контейнер
    
$container ABC::newService(ABC::CONTAINER);

    
// Помещаем класс MyClass в контейнер, используя имя класса        
    
$container->addGlobal('MyClass'MyClass::class);     
    
    
// Смотрим.    
    
$obj $container->get('MyClass'); 
    echo 
$obj->checkTime();    // .....10.201  
    
echo '<br>'
    
// Инициализируем второй контейнер

    
$container ABC::newService(ABC::CONTAINER);
    
    
// Можно получить сервис из первого.    
    
$obj $container->get('MyClass'); 
    echo 
$obj->checkTime();    // .....10.201  
    
echo '<br>';    




К оглавлению
Типы внедрений #.

Существуют три типа внедрения зависимостей:
1. Через конструктор
2. Через сеттеры
3. Через публичные свойства

Все они имеют свои преимущества и недостатки, кроме того, в разных библиотеках могут использоваться разные типы. В контейнере имеется возможность выбора типа внедрения как для всех сервисов, так и выборочно, методами typeConstruct(), typeMethodCall() и typePropertyes(). Принудительно устанавливать тип внедрения через конструктор не нужно, так как он установлен по умолчанию. Однако если большинство сервисов используют сеттеры, можно установить тип typeMethodCall(true), а методом typeConstruct() установить тип для отдельных сервисов.
(см. пример).


1. Внедрение через конструктор - самый популярный тип внедрения, поэтому он реализован по умолчанию. Преимуществом его является то, что инициализация свойств с зависимостями происходит в одном месте. Это компактно и наглядно.

Недостатками является то, что в таком случае важен порядок установки зависимостей, что затрудняет работу с наследованием зависимостей и дефолтными блоками.
Еще есть вероятность "телескопического конструктора", когда необходимо передать большое количество зависимостей.
Кроме того, в класее-реципиенте уже может быть реализован конструктор, и внедрение свойств таким способом становится невозможным.


2. Этих недостатков лишен способ внедрения через сеттеры. Кроме того, в контейнере применен строгий тип реализации сеттеров. А значит есть дополнительная проверка их наличия. Если зависимость не нужна в сервисе, можно отменить ее, установив в false, либо реализовать пустой сеттер.

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


3. Третий способ - через публичные свойства. Он не имеет никаких преимуществ, только недостатки:

Свойства остаются доступными на протяжении всего жизненного цикла объекта.
Невозможно применить типизирование.

К единственному сомнительному преимуществу можно отнести простоту реализации, поэтому некоторые библиотеки реализованы именно так. Для работы с такими библиотеками этот способ присутствует в контейнере.

Примеры

Конструктор. Что бы принять зависимость по этому типу, в классе-реципиенте необходимо реализовать конструктор, который принимает аргументами зависимости.
1
2
3
4
5
6
7
8
9
10
11
12
13

class Recepient
{
    protected 
$example;
    protected 
$std;    

    public function 
__construct(Example 
$exampleStdClass $std)
    {
        
$this->example $example;
        
$this->std $std;
    } 
}





Сеттеры. Если для контейнера, или отдельного реципиента, включена опция внедрения через сеттеры, они должны быть установлены в классе. Сеттер формируется из преффикса set и имени свойства. Такие сеттеры вызываются контейнером автоматически.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class Recepient
{
    protected 
$example;
    protected 
$std;  
    
// Простой сеттер    
    
public function setExample(Example $example)
    {
        
$this->example $example;
    }
// Сеттер с проверкой    
    
public function setStd(StdClass $std)
    {
        if(empty(
$this->std)){
            
$this->std $std;
        }
    }    
}





Внедрение напрямую в свойства. Для этого способа нужно просто объявить свойства зависимостей публичными.
1
2
3
4
5
6
7

class Recepient
{
    public 
$example;
    public 
$std;  




Методы установки типов можно реализовать в любом месте скрипта до вызова методов get() и getNew()
Важно!
Mетод установки типа внедрения с аргументом true можно вызвать только один раз. Другими словами, установить дефолтный тип можно только единожды. Все остальные попытки будут проигнорированы.



К оглавлению
Внедрение зависимости в контейнере #.

Классы для примера
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

    
 
// Класс-реципиент
class Recepient
{
    protected 
$example;
    protected 
$std;    

    public function 
__construct(Example $example\StdClass $std)
    {
        
$this->example $example;
        
$this->std $std;
    } 

    public function 
run()
    {
        
$this->example->display();
    }    
}

// Класс-зависимость
class Example
{
    public function 
display()
    {
        echo 
'Я Example!';
    }
}
    
    




Прямое внедрение зависимости методом addDependences()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

    
// Создаем сервис     
    
$container->add('Recepient'Recepient::class);
             
// Добавляем зависимости для класса Recepient 
    
$container->addDependences('Recepient', [  // по имени класса
                                               
'example' => Example::class,
                                               
// с использованием замыкания
                                               
'std'     => function(){ 
                                                                return new \
StdClass;
                                                            }
                                            ]);    
//
    
$obj $container->get('Recepient');     
    
$obj->run(); // Я Example    
    




Для каждого сервиса будет инстанцирован собственный новый объект зависимости. Но часто нужно, что бы зависимость была общей. Допустим драйвер базы данных не стоит делать новым объектом для каждого сервиса, так как будет организован новый коннект. В таком случае такой класс можно зарегистрировать как сервис, и внедрять его по алиасу. Он будет внедрен в реципиент вместе со своими зависимостями. А так как вызов сервиса всегда возвращает один и тот же объект, при инициализации зависимости будет использован он и первоначальные объекты зависимостей.

Причем не важно, когда он был установлен, до или после регистрации сервиса-реципиента. Это упрощает работу с дефолтными блоками.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// Регистрируем зависимости как сервисы    
    
$container->add('Example'Example::class);
    
$container->add('Std', \StdClass::class);
    
 
// Создаем сервис Recepient    
    
$container->add('Recepient'Recepient::class);
          
// Внедряем сервисы как зависимости
    
$container->addDependences('Recepient', [
                                               
'example' => 'Example',
                                               
'std'     => 'Std'
                                            
]);

// Смотрим
    
$obj $container->get('Recepient');     
    
$obj->run(); // Я Example!     


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



К оглавлению

Общие зависимости (дефолтные блоки) #.

Бывают ситуации, когда некоторые зависимости требуются во всех или почти во всех сервисах, зарегистрированных в контейнере. Что бы избежать повтореня кода и сделать его более компактным, можно объявить их дефолтными. Тогда они автоматически будут внедрены во все сервисы.

Сделать это можно с помощью метода addDefault()

Дефолтный блок должен быть установлен выше регистрации сервисов, которые будут наследовать его зависимости.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// Устанавливаем общие зависимости
    
$container->addDefault([
                               
'example' => Example::class,
                               
'std'     => \stdClass::class
                           ]);

 
// Создаем сервис     
    
$container->add('Recepient'Recepient::class);
          
// Смотрим
    
$obj $container->get('Recepient');     
    
$obj->run(); // Я Example! 
  




Если какая то зависимость из дефолтного блока не нужна в сервисе, ее можно отменить методом addDependences(), установив ее значение в false или null. Можно так же перезаписать зависимость, установив этим методом другую.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// Устанавливаем общие зависимости
    
$container->addDefault([
                               
'example' => Example::class,
                               
'std'     => \stdClass::class
                           ]);

 
// Создаем сервис     
    
$container->add('Recepient'Recepient::class);
          
          
// Отменяем одну зависимость для сервиса Recepient
    
$container->addDependences('Recepient', [
                                               
'std' => false
                                           
]);




Еще можно установить другой дефолтный блок, зависмости которого заменят или отменят дефолтные, установленные раньше.

Если общие зависимости больше не нужны, можно очистить все ранее установленные дефолтные блоки методом clearDafault(), и, при желании, установить новые.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// Устанавливаем общие зависимости
    
$container->addDefault([
                               
'example' => Example::class,
                               
'std'     => \stdClass::class
                           ]);

 
// Создаем сервис     
    
$container->add('Recepient'Recepient::class);
          
// Смотрим
    
$obj $container->get('Recepient');     
    
$obj->run(); // Я Example! 
    
// Очищаем дефолтный блок
    
$container->clearDefault(); 

 
// Создаем другой сервис в котором уже нет дефолтных зависимостей    
    
$container->add('OtherRecepient'Recepient::class);

// Смотрим
    
$obj $container->get('OtherRecepient');     
    
$obj->run(); // Too few arguments to function Recepient::__construct() 





К оглавлению
Наследование зависимостей #.

Для избегания повторов кода, кроме установки дефолтных блоков, можно еще наследовать зависимости у ранее установленных сервисов с помощью метода extendsServices(). Причем не важно, наследуется сервис на стороне PHP или нет.

Если класс сервиса не расширяет класс, от которого наследуются зависимости на стороне PHP, для них должны быть реализованы конструктор, сеттеры или установлены публичные свойства, в зависимости от выбранного типа внедрения.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

 
// Создаем сервис с зависимостью
    
$container->add('Recepient'Recepient::class) 
              ->
addDependences('Recepient', [
                                   
'example' => Example::class,
                                   
'std'     => \stdClass::class
                               ]);
                                           

// Создаем другой сервис и наследуем зависимости от первого
    
$container->add('OtherRecepient'Recepient::class )
              ->
extendsServices('Recepient');
           
           
// Смотрим
    
$obj $container->get('OtherRecepient');     
    
$obj->run(); // Я Example!




Наследовать зависимости можно от нескольких сервисов сразу. Их алиасы нужно передать аргументами в метод extendsServices()
1
2
3
4
5

 
// Наследуем зависимости от двух сервисов: Recepient и SomeService
    
$container->add('OtherRecepient'Recepient::class )
              ->
extendsServices('Recepient''SomeService');




Важно!
Метод extendsServices() может быть вызван только после метода add() или addGlobal(), так как наследует зависимости для сервиса, установленного этим методом. В противном случае будет сгенерирована ошибка.

К оглавлению
Сервис-локатор #

Паттерн "контейнер зависимостей" (DIC) отличается от сервис-локатора (ServiceLocator) только способом использования. Контейнер настраивает зависимости в точке сборки приложения либо компонента (Composition Root). Сервис-локатор может сам передаваться в класс как зависимость.

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

Для того, чтобы ограничить доступ к основным сервисам, можно локализовать некоторые из них в локаторе и передавать зависимостью его. Сгенерировать локатор и поместить в него сервисы можно с помощью метода createLocator()

Для примера изменим класс-реципиент:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

 
// Класс-реципиент
class Recepient
{
    protected 
$locator;  

    public function 
__construct(ContainerInterface $locator)
    {
        
$this->locator $locator;
    }

    public function 
run()
    {
        
$example $this->locator->get('Example');
        
$example->display();
        
// Этот код вызовет исключение, если сервис Recepient 
        // не зарегистрирован в локаторе
        // $example = $this->locator->get('Recepient');
    
}    
}




Использование внутреннего сервис локатора
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

    
// Регистрируем зависимость как сервис
    
$container->add('Example'Example::class);
    
    
// Генерируем локатор и помещаем сервис в него 
    
$container->createLocator('Locator''Example');
    
    
// Регистрируем основной сервис и передаем в него зависимостью локатор
    
$container->add('Recepient'Recepient::class)
              ->
addDependences('Recepient', ['locator' => 'Locator']);
              
    
// Запускаем
    
$obj $container->get('Recepient'); // Я Example!
    
$obj->run();



Можно сгенерировать несколько локаторов под разными идентификаторами. Каждый локатор может быть установлен только один раз.

Важно!
Хотя имеется возможность поместить в локатор зарегистрированные сервисы, лучше использовать для генерации локатора отдельную карту зависимостей.


К оглавлению
Вспомогательные методы #

Что бы избежать перезаписи, зависимости для каждого сервиса можно устанавливать только один раз. Контейнер сам объявляет сервис синтетическим после первого внедрения.

Но иногда нужно запретить инъекции в сервис, у которого зависимости не устанавливались. Что бы это сделать, используйте метод serviceSynthetic(), который объявит сервис синтетическим. При попытке установить для него зависимость будет сгенерирована ошибка.

Проверить, является ли сервис синтетическим, можно методом isSynthetic()

Иногда возникает потребность вызвать деструктор класса до завершения скрипта, уничтожая объект функцией unset(). Но если сервис заявлен как глобальный, то его объект хранится в контейнере, и ссылка на него не уничтожится. Cоответственно деструктор вызван не будет до завершения скрипта. Для того, что бы принудительно вызвать деструктор, нужно уничтожить объект в хранилище. Сделать это можно методом unsetObject().


Этот метод можно использовать для экономии памяти, уничтожая отработанные объекты.

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

Добавим в класс-пример Recepient деструктор.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

    
 
// Класс-реципиент
class Recepient
{
    protected 
$example;
    protected 
$std;    

    public function 
__construct(Example $exampleStdClass $std)
    {
        
$this->example $example;
        
$this->std $std;
    } 

    public function 
run()
    {
        
$this->example->display();
    } 
    
    public function 
__destruct()
    {
        echo 
'<br>Вызван деструктор'
    }
}
    
    




Демонстрация
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

// Создаем глобальный сервис     
    
$container->addGlobal('Recepient'Recepient::class)
              ->
addDependences('Recepient', [
                                               
'example' => Example::class,
                                               
'std'     => \stdClass::class
                                            ]);
    
// Объявляем сервис синтетическим
    
$container->serviceSynthetic('Recepient');
// Проверяем
    
var_dump($container->isSynthetic('Recepient'));    // true
    
// Получаем объект сервиса
    
$obj $container->get('Recepient');     
// Уничтожаем объект в скрипте
    
unset($obj);    
// Уничтожаем объект в контейнере
    
$container->unsetObject('Recepient'); // Вызван деструктор
    
    
echo '<br>Продолжение скрипта<br>';    
    
$obj $container->get('Recepient'); 
    
$obj->run(); // Я Example! 
// Вызван деструктор




К оглавлению
Карта зависимостей #

Удобным способом конфигурации контейнера является использование так называемых "карт зависимостей". Карта, это обычный массив, который можно поместить в отдельный файл в конфигурации приложения. Этот массив можно передать аргументом в специальный метод setMaps(), а так же методы add(), addGlobal() или createLocator().

При формировании массива крты нужно использовать следующий синтаксис:
  • 1. Ключами являются идентификаторы сервисов, регистрируемых в контейнере.
  • 2. Значениями могут являться:
    • 2.1. Callback-функция (замыкание), которая должна возвращать инстант класса сервиса
    • 2.2. Имя класса сервиса вместе с немспейсом
    • 2.3. Готовый объект сервиса
    • 2.4. Массив с устанавливаемыми зависимостями.
  • 3. Если в значении указывается массив, он формируется следующим образом:
    • 3.1. Элементом без ключа является сам сервис. В нем можно использовать все, что описано в п.2
    • 3.2. Ключами являются имена свойств, в которые будут переданы зависимости
    • 3.3. Значениями можно использовать все, описанное в п.п.2 кроме п.2.4
    • 3.4. Кроме того, для значения зависимости можно использовать null или false, если требуется отменить унаследованную ранее зависимость




К оглавлению
Примеры использования карт зависимостей #


Пример простой карты #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

    $map 
= [
        
'Recepient' => [
       
// Элемент без строкового ключа, это класс сервиса
            
Recepient::class,
            
// Простое внедрение зависимости
                
'example' => Example::class,
            
// Упаковка зависимости в замыкание
                
'std'     => function(){
                                return new \
StdClass;
                             }
        ],
    ];
// Устанавливаем карту в контейнер
    
$container->setMaps($map);
    
    
$obj $container->get('Recepient'); 
    
$obj->run(); // Я Example! 




Внедрение зависимостями зарегистрированных сервисов
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

    $map 
= [
    
// Регистрируем сервисы
       
'Example' => Example::class,
       
'Std'     => function(){
                        return new \
StdClass;
                    },
    
        
'Recepient' => [
            
Recepient::class,
            
// Внедряем их зависимостями
                
'example' => 'Example',
                
'std'     => 'Std'   
        
],
    ];
// Можно установить карту с помощью метода add()
    
$container->add($map);
    
    
$obj $container->get('Recepient'); 
    
$obj->run(); // Я Example! 
  




К оглавлению
Дефолтный блок #


В картах зависмостей можно использовать дефолтные блоки. Для этого нужно активировать дефолтный блок с зарезервированным иденификатором __default.

Действует это аналогично методу addDefault().

Важно!
Так как карта заполняется декларативно, в одной карте можно использовать только один дефолтный блок. Все остальные будут проигнорированы.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

    $map 
= [
        
'__default' => [
                           
'example' => Example::class,
                           
'std'     => function(){
                                            return new \
StdClass;
                                         } 
        ],    
     
        
'Recepient' => Recepient::class,
     
    ];
   
    
$container->add($map); 

    
// Запускаем
    
$obj $container->get('Recepient'); 
    $obj->run(); // Я Example!




К оглавлению
Наследование зависимостей #

При формировании карты можно использовать наследование зависимостей по аналогии с методом extendsService() с одним отличием. Так как карта заполняется декларативно, порядок расположения сервисов в ней не имеет значение.

Можно наследовать зависимости от нескольких сервисов сразу, перечислив их через запятую.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

    $map 
= [
     
// Регистрируем сервис с зависимостями
        
'Service' =>  [
                    
Recepient::class,
                           
'example' => Example::class,
                           
'std'     => function(){
                                            return new \
StdClass;
                                         } 
        ],
        
     
// Наследуем от него зависимости
        
'Recepient extends Service' => Recepient::class,
     
    ];
   
    
$container->add($map); 
    
// Запускаем
    
$obj $container->get('Recepient'); 
    
$obj->run(); // Я Example!