Следует стандартам PSR-11, а также PSR-1, PSR-2, PSR-4.
Основные идеи:
- Создание объектов, указанных в конфигурации
- Создание объектов, не указанных в конфигурации (в том числе рекурсивно - на любой уровень вложенности)
- Возможность осуществления вызовов и установки свойств после создания объекта
- Возможность осуществления вызовов и установки свойств по классу/интерфейсу (например, "Aware"-интерфейсы)
- Создание объектов с помощью фабрик
- Алиасы (псевдонимы, теги) для любого сервиса
- Отключаемая возможность анализа зависимостей методов/конструкторов с помощью рефлексии
- Возможность добавить дочерние контейнеры для получения объектов, не описанных в текущем контейнере
Создание объекта контейнера:
$container = new Container();С помощью указания собственных реализаций зависимостей контейнера вы можете расширять или изменять его функциональность. Возможность изменения поведения контейнера.
Добавление описания требует указания уникального имени добавляемого сервиса.
$definition = new Definition(SimpleComponent::class);
$definition = $container->addDefinition('simple', $definition);Далее, по указанному имени может быть:
- указана ссылка на описание сервиса
- произведено получение объекта из контейнера
- определено соответствие любого класса и описанного сервиса
- произведено добавление псевдонима к описанию сервиса
Является аналогом метода addDefinition, но позволяет вместо объекта Definition передать имя класса:
$definition = $container->addDefinitionClass('simple', SimpleComponent::class);Добавление любого значения в контейнер для последующего использования:
$container->addScalar('main_email', 'some@email.com');При попытке получить значение из контейнера по имени вернется указанное значение:
$email = $container->get('main_email');
// $email = 'some@email.com'Возможность указать, что при запросе определенного класса будет возращен объект по описанию, добавленному ранее.
Например, мы добавили описание нашего простейшего сервиса:
$definition = new Definition(SimpleComponent::class);
$definition = $container->addDefinition('simple', $definition);И хотим чтобы при запросе класса SimpleComponentInterface::class возвращался бы объект,
созданный с помощью описания (Definition), который мы добавили ранее.
Указываем соответствие класса SimpleComponentInterface::class и Definition:
$container->addReference('simple', SimpleComponentInterface::class);Теперь, при запросе объекта по класс SimpleComponentInterface::class контейнер будет возвращать объект,
созданный с помощью указанного описания (Definition).
Возможность указать псевдоним/псевдонимы для существующего описания по имени.
Например, мы добавили описание нашего простейшего сервиса:
$definition = new Definition(SimpleComponent::class);
$definition = $container->addDefinition('simple', $definition);И хотим чтобы при запросе по имени "simple_object" возвращался бы объект, созданный с помощью описания (Definition), который мы добавили ранее.
Добавляем псевдоним:
$container->addAlias('simple', 'simple_object');Теперь, при запросе объекта по имени "simple_object" контейнер будет возвращать объект, созданный с помощью указанного описания (Definition).
Так же поддерживается добавление нескольких псевдонимов (тегов) одновременно:
$container->addAlias('simple', [
'simple_object',
'smart'
]);Позволяют конфигурировать созданные объекты определенного вида:
- Устанавливать свойства
- Выполнять методы
Весьма удобны с "Aware" - интерфейсами.
$inflection = new Inflection(LoggerAwareInterface::class);
$inflection->addCall('setLogger', $myLogger);
$container->addInflection($inflection);Позволяет извлекать сервисы из дочерних контейнеров, если они не найдены в текущем
$container->addDelegate($anotherContainer);Осуществляет вызов callable с инъекцией необходимых зависимостей.
Пример класс:
class Example {
public $simpleComponent;
public function setSimpleComponent(SimpleComponent $simpleComponent)
{
$this->simpleComponent = $simpleComponent;
}
}Вызов:
$example = new Example();
$container->invoke([$example, 'setSimpleComponent']);
// $example->simpleComponent instanceof SimpleComponent = true
С передачей аргументов:
$example = new Example();
$container->invoke([$example, 'setSimpleComponent'], [
'simpleComponent' => new SimpleComponent()
]);
// $example->simpleComponent instanceof SimpleComponent = true
Получение объекта по имени, алиасу (псевдониму/тегу), классу, родительским классам или интерфейсам.
$simpleComponent = $container->get(SimpleComponent::class);
$exampleComponent = $container->get('example');
Проверка на то, присутствует ли объект/описание в контейнере либо в дочерних контейнерах.
$hasSimpleComponent = $container->has(SimpleComponent::class);
$hasExampleComponent = $container->has('example');
Необходим для описания конфигурирования создания объектов.
Обязательным параметром конструктора является имя класса, объект которого будет создан.
$definition = new Definition(SimleComponent::class);
Установка аргументов конструктора.
Все не переданные аргументы со значениями по-умолчанию будут заменены на значения по-умолчанию. Все не переданные аргументы с указанием определенного класса будут извлечены из контейнера.
$definition->setArguments([
'username' => 'admin',
'password' => 'mypassword'
]);
Также, в аргументах конструктора можно использовать ссылки на описания других сервисов.
$definition->setArguments([
'simpleService' => '@simple'
]);
Установка свойств будет осуществлена после создания объекта.
$definition->addProperty('username', 'admin');
$definition->addProperty('password', 'mypassword');Вызов метода будет осуществлен после создания объекта и установки свойств
$definition->addCall('setLogin', 'admin');Также, в аргументах метода можно использовать ссылки на описания других сервисов.
$definition->addCall('setSimpleService', '@simple');Важно! Необходимо устанавливать псевдонимы (теги) до передачи описания (Definition) в контейнер.
Впоследствии, по указанным псевдонимам (тегам) можно будет ссылаться на описание сервиса, либо получать объект из контейнера.
Указание псевдонима (тега):
$definition->addAlias('another_name');или (сразу несколько):
$definition->addAliases(['another_name', 'second_name']);Можно указать, если для создания объекта необходимо вызвать статический метод класса.
class Example {
public static function create()
{
return new Example();
}
}$definition = new Definition(Example::class);
$definition->setConstructMethod('create');Данный метод так же применяется и для работы с фабриками.
Поддерживается указание фабрики для создания объектов.
Пример:
$definition = new Definition(Example::class);
$definition->setFactory(function () {
return new Example();
});Фабрика:
class ExampleFactory {
public function __invoke(){
return new Example();
}
}Контейнер:
$container->addDefinitionClass('exampleFactory', ExampleFactory::class);
$definition = new Definition(Example::class);
$definition->setFactory('@exampleFactory');Фабрика:
class ExampleFactory {
public function __invoke(){
return new Example();
}
}Контейнер:
$definition = new Definition(Example::class);
$definition->setFactory(new ExampleFactory());Фабрика:
class ExampleFactory {
public function createExample(){
return new Example();
}
}Контейнер:
$container->addDefinitionClass('exampleFactory', ExampleFactory::class);
$definition = new Definition(Example::class);
$definition->setFactory('@exampleFactory');
$definition->setConstructMethod('createExample');Фабрика:
class ExampleFactory {
public function createExample(){
return new Example();
}
}Контейнер:
$container->addDefinitionClass('exampleFactory', ExampleFactory::class);
$definition = new Definition(Example::class);
$definition->setFactory(ExampleFactory::class);
$definition->setConstructMethod('createExample');Фабрика:
class ExampleFactory {
public function createExample(){
return new Example();
}
}Контейнер:
$container->addDefinitionClass('exampleFactory', ExampleFactory::class);
$definition = new Definition(Example::class);
$definition->setFactory(['@exampleFactory', 'createExample']);Фабрика:
class ExampleFactory {
public function createExample(){
return new Example();
}
}Контейнер:
$container->addDefinitionClass('exampleFactory', ExampleFactory::class);
$definition = new Definition(Example::class);
$definition->setFactory([ExampleFactory::class, 'createExample']);Внимание! По-умолчанию со стандартным описанием (Definition) объект считается "общим" (shared)
Если необходимо, чтобы объект создался один раз при первом запросе, а впоследствии возвращался один и тот же экземпляр объекта (например, подключение к базе данных), то можно указать что объект является "общим":
$definition = new Definition(DatabaseConnection::class);
$definition->setShared(true);Если нужно указать, что при каждом запросе объекта должен возвращаться новый экземпляр,
то вызываем метод setShared с параметром false:
$definition = new Definition(Mail::class);
$definition->setShared(false);Контейнер позволяет конфигурировать (вызывать методы и устанавливать свойства) объектам одного типа.
Описанием такой конфигурации является объект Inflection.
Конструктор принимает класс, которому должен соответствовать созданный объект для применения конфигурации:
$inflection = new Inflection(SimpleInterface::class);После создания объекта контейнер обойдет все объекты конфигурации и применит подходящие из них.
Установка свойств буде осуществлена в момент применения объекта конфигурации.
$inflection->addProperty('username', 'admin');
$inflection->addProperty('password', 'mypassword');Вызов методов будет осуществлен в момент применения объекта конфигурации после применения свойств.
$inflection->addCall('setLogin', 'admin');Также, в аргументах метода можно использовать ссылки на описания других сервисов.
$inflection->addCall('setSimpleService', ['@simple']);В аргументах методов и конструкторов можно указывать ссылки на тот или иной сервис. Это осуществляется передачей в качестве параметра строки, в которой содержится символ "@" и далее следует имя или псевдоним (тег) сервиса.
Например, в контейнере есть описание нескольких сервисов:
$container->addDefinitionClass('fileLogger', Logger::class);
$container->addDefinitionClass('mailLogger', Logger::class);Есть класс, который требует объект в качестве зависимости:
class Example
{
public function __construct(Logger $logger)
{
// ...
}
}В конструктор класса можно передать ссылку на определенный сервис:
$definition = new Definition(Example::class);
$definition->setArguments([
'logger' => '@fileLogger'
]);При создании объекта Example в качестве логгера будет установлен объект,
который создан по описанию (Definition) с именем "fileLogger".
Аналогичным образом работает указание ссылок в аргументах метода (и для Definition и для Inflection):
$definition->addCall('setLogger', [
'logger' => '@fileLogger'
]);Если по переданному имени невозможно получить объект, будет выброшено исключение.
Для того чтобы указать опциональную зависимость, необходимо перед именем (псевдонимом/тегом) указать "@?".
Например, имеется метод:
class Example
{
public function setLogger(Logger $logger = null)
{
// ...
}
}В описании сервиса укажем:
$definition->addCall('setLogger', [
'logger' => '@?fileLogger'
]);В таком случае если компонент по имени (псевдониму/тегу) будет найден - он будет подставлен. Если он не будет найден - будет подставлено значение по-умолчанию.
Ссылку на сервис можно так же использовать и при указании фабрики для создания объекта.
Например в этом примере для создания объекта будет использоваться
метод createObject сервиса с именем (тегом/псевдонимом) "myFactory":
$definition->setFactory('@myFactory');
$definition->setConstructMethod('createObject');Другой вариант (массив - ссылка на сервис и его метод):
$definition->setFactory(['@myFactory', 'createObject']);Или (если "myFactory" является Invokable объектом):
$definition->setFactory('@myFactory');По-умолчанию Container (а точнее его часть - DefinitionAggregate) автоматически анализирует все родительские классы и интерфейсы добавляемого сервиса.
Если у контейнера будет запрошен объект по интерфейсу или родительскому классу, то вернется соответствующий объект.
Например, есть класс:
class Example extends ExampleParent implements ExampleInterface
{
// ...
}Добавим его в контейнер:
$definition = new Definition(Example::class);
$container->addDefinition('example', $definition);Теперь, при попытке получить объект по классам ExampleParent::class и ExampleInterface::class
будет получен объект созданный по описанию, добавленному нами выше:
$object = $container->get(ExampleParent::class);
// get_class($object) = Example::class
$object = $container->get(ExampleInterface::class);
// get_class($object) = Example::classОтключить данное поведение можно следующим образом:
$definitionAggregate = new DefinitionAggregate();
$definitionAggregate->setAnalyzeReferences(false);
$container = new Container(null, $definitionAggregate);По-умолчанию контейнер будет пытаться автоматически создать необходимые объекты, даже если они не были описаны в контейнере.
Например, имеем класс:
class Example
{
public function setLogger(Logger $logger = null)
{
// ...
}
}Добавим его описание в контейнер:
$definition = new Definition(Example::class);
$container->addDefinition('example', $definition);При попытке получить объект Example::class контейнер попытается найти описание подходящего класса,
необходимого для создания объекта (в нашем случае это Logger::class).
Если найти подходящее описание невозможно, контейнер попытается создать этого класса.
Отключить это поведение можно следующим образом:
$container = new Container();
$container->setAutoWire(false);По-умолчанию Container (а точнее его часть - Builder) анализирует все аргументы методов и конструкторов, что позволяет автоматически подставлять необходимые зависимости.
Отключить это поведение можно следующим образом:
$builder = new Builder(false);
$container = new Container($builder);Структура контейнера:
- Container
- BuilderInterface
- DependenciesResolverInterface
- DefinitionAggregateInterface
- BuilderInterface
Все зависимости являются заменяемыми и расширяемыми.