fi1a / dependency-injection
Dependency injection container
Requires
- php: ^7.3 || ^8
- fi1a/collection: ^2.0
- fi1a/hydrator: ^1.0
Requires (Dev)
- captainhook/captainhook: ^5.11
- phpunit/phpunit: ^9.5
- slevomat/coding-standard: ^8.6
- squizlabs/php_codesniffer: ^3.7
- vimeo/psalm: ^4.3
README
Контейнер dependency injection, может разрешать зависимости, создавать экземпляры и настраивать классы. Поддерживает внедрение конструктора, свойств и методов.
Установка
Установить этот пакет можно как зависимость, используя Composer.
composer require fi1a/dependency-injection
Использование контейнера
Для использования контейнера dependency injection, сначала необходимо в конфигурацию задать определения созданное с помощью builder'а.
Название определения обычно является именем интерфейса. Когда запрашивается тип для создания объекта, будет использоваться это определение.
Это происходит при вызове метода get
непосредственно из контейнера. Объекты также создаются не явно при разрешении зависимостей.
use Fi1a\DI\Container; use Fi1a\DI\ContainerConfig; use Fi1a\DI\Builder; use Fi1a\Unit\DI\Fixtures\ClassA; use Fi1a\Unit\DI\Fixtures\ClassAInterface; use Fi1a\Unit\DI\Fixtures\ClassC; use Fi1a\Unit\DI\Fixtures\ClassCInterface; $config = new ContainerConfig(); $config->addDefinition( Builder::build(ClassAInterface::class) ->defineClass(ClassA::class) ->getDefinition() ); $config->addDefinition( Builder::build(ClassCInterface::class) ->defineClass(ClassC::class) ->defineConstructor([1, true]) ->getDefinition() ); $container = new Container($config); $container->get(ClassCInterface::class); // ClassCInterface
Объект может быть определен несколькими способами:
- defineClass - сопоставление с конкретным классом;
- defineFactory - если реализация сложная и может быть лучше описана в коде, то следует использовать фабричный метод. При использовании фабричного метода, зависимости в аргументах автоматически разрешаются.
- defineObject - вернуть созданный экземпляр объекта.
Также доступны следующие определения:
- defineConstructor - задает аргументы для конструктора класса определенного как defineClass;
- defineProperty - задает значение свойства объекта;
- defineProperties - задает ассоциативный массив со значениями свойств объекта;
- defineMethod - задает метод объекта, который необходимо вызвать с объявленными аргументами;
- defineMethods - задает ассоциативный массив с методами объекта, которые необходимо вызвать с объявленными аргументами.
При отсутствии определения для запрашиваемого типа, контейнер выбросит исключение Fi1a\DI\Exceptions\NotFoundException
.
defineClass
Сопоставление с конкретным классом, определение аргументов конструктора, задание свойств и вызов методов:
use Fi1a\DI\Container; use Fi1a\DI\ContainerConfig; use Fi1a\DI\Builder; use Fi1a\Unit\DI\Fixtures\ClassA; use Fi1a\Unit\DI\Fixtures\ClassAInterface; $config = new ContainerConfig(); $config->addDefinition( Builder::build(ClassAInterface::class) ->defineClass(ClassA::class) ->defineConstructor([ 'parameter1' => 10, ]) ->defineProperty('property1', 100) ->defineMethod('setProperty2', [true]) ->getDefinition() ); $container = new Container($config); /** @var ClassA $object */ $object = $container->get(ClassAInterface::class); // ClassAInterface $object->property1; // 100 $object->property2; // true
defineFactory
Используется замыкание как фабричный метод:
use Fi1a\DI\Container; use Fi1a\DI\ContainerConfig; use Fi1a\DI\Builder; use Fi1a\Unit\DI\Fixtures\ClassA; use Fi1a\Unit\DI\Fixtures\ClassAInterface; use Fi1a\Unit\DI\Fixtures\ClassB; $config = new ContainerConfig(); $config->addDefinition( Builder::build(ClassAInterface::class) ->defineFactory(function (ClassB $classB) { $instance = new ClassA($classB); $instance->property1 = 100; $instance->property2 = true; return $instance; }) ->getDefinition() ); $container = new Container($config); /** @var ClassA $object */ $object = $container->get(ClassAInterface::class); // ClassAInterface $object->property1; // 100 $object->property2; // true
Использование фабричного метода в классе:
use Fi1a\DI\Container; use Fi1a\DI\ContainerConfig; use Fi1a\DI\Builder; use Fi1a\Unit\DI\Fixtures\ClassA; use Fi1a\Unit\DI\Fixtures\ClassAInterface; use Fi1a\Unit\DI\Fixtures\FactoryA; $config = new ContainerConfig(); $config->addDefinition( Builder::build(ClassAInterface::class) ->defineFactory([FactoryA::class, 'factoryStatic']) ->getDefinition() ); $container = new Container($config); /** @var ClassA $object */ $object = $container->get(ClassAInterface::class); // ClassAInterface $object->property1; // 100 $object->property2; // true
defineObject
Использовать уже созданный экземпляр объекта:
use Fi1a\DI\Container; use Fi1a\DI\ContainerConfig; use Fi1a\DI\Builder; use Fi1a\Unit\DI\Fixtures\ClassB; use Fi1a\Unit\DI\Fixtures\ClassBInterface; $config = new ContainerConfig(); $config->addDefinition( Builder::build(ClassBInterface::class) ->defineObject(new ClassB()) ->getDefinition() ); $container = new Container($config); /** @var ClassB $object */ $object = $container->get(ClassBInterface::class); // ClassBInterface
Хелпер di
Доступен хелпер di()
, возвращающий один экземпляр контейнера для регистрации опредлений в других пакетах.
use Fi1a\DI\Builder; use Fi1a\Unit\DI\Fixtures\ClassA; use Fi1a\Unit\DI\Fixtures\ClassAInterface; use Fi1a\Unit\DI\Fixtures\ClassC; use Fi1a\Unit\DI\Fixtures\ClassCInterface; di()->config()->addDefinition( Builder::build(ClassAInterface::class) ->defineClass(ClassA::class) ->getDefinition() ); di()->config()->addDefinition( Builder::build(ClassCInterface::class) ->defineClass(ClassC::class) ->defineConstructor([1, true]) ->getDefinition() ); di()->get(ClassCInterface::class); // ClassCInterface
Создание определения из массива
Для создания определения из массива можно воспользоваться методом buildFromArray
класса реализующего интерфейс Fi1a\DI\ArrayBuilderInterface
:
use Fi1a\DI\ArrayBuilder; use Fi1a\DI\Container; use Fi1a\DI\ContainerConfig; use Fi1a\Unit\DI\Fixtures\ClassA; use Fi1a\Unit\DI\Fixtures\ClassAInterface; $config = new ContainerConfig(); $config->addDefinition( $definition = ArrayBuilder::buildFromArray([ 'name' => ClassAInterface::class, 'class_name' => ClassA::class, 'constructor' => [100, true], 'properties' => [ 'property1' => 100, 'property2' => true, ], 'methods' => [ 'setProperty1' => [100], 'setProperty2' => [true], ], ])->getDefinition() ); $container = new Container($config); /** @var ClassA $object */ $object = $container->get(ClassAInterface::class); // ClassAInterface
Преобразование определения и коллекций в массив
Для преобразования определений и коллекций в массив, можно воспользоваться методом definition
, или collection
класса реализующего интерфейс Fi1a\DI\ToArrayInterface
:
use Fi1a\DI\ContainerConfig; use Fi1a\DI\Builder; use Fi1a\DI\ToArray; use Fi1a\Unit\DI\Fixtures\ClassA; use Fi1a\Unit\DI\Fixtures\ClassAInterface; $config = new ContainerConfig(); $definition = Builder::build(ClassAInterface::class) ->defineClass(ClassA::class) ->defineConstructor([ 'parameter1' => 10, ]) ->defineProperty('property1', 100) ->defineMethod('setProperty2', [true]) ->getDefinition(); $config->addDefinition($definition); $toArray = new ToArray(); $array = $toArray->collection($config->getDefinitions()); // [[...], [...]] $arrayDefinition = $toArray->definition($definition); // [...]