Inversion это простой и функциональный контейнер внедрения зависимости для PHP 5.3. Поддерживает сервис-ориентированную архитектуру, ссылки, PRS-0, и Composer.
Установить можно через packagist.org: granula/inversion либо скачав и добавив к PRS-0 совместимому загрузчику.
$container = new InversionContainer();
$container['foo'] = 'MyClassFoo';
// ...
$foo = $container('foo');
В вышеприведённом примере показана базовая функциональность контейнера. Разберем что там происходит.
В первой строчки создаем экземпляр контейнера. Во второй создаем ассоциацию между «foo» и сервисом создающим экземпляр класса «MyClassFoo». Что по другому можно записать так:
$container->addService(new Service('MyClassFoo'), 'foo');
Имя «foo» идёт вторым, т.к. его вообще можно опустить. Подробнее ниже.
В третей строчке мы получаем экземпляр объекта. Что по другому можно записать так:
$foo = $container('foo');
// или
$foo = $container->get('foo');
// или
$foo = $container['foo']->get();
// или
$foo = $container->getService('foo')->get();
Однако, рекомендую использовать сокращённый вариант, хотя все они допустимы.
Описание зависимостей
По умолчанию когда в контейнер передаётся строка она понимается как имя класса и подставляется в сервис InversionServise.
У данного сервиса есть несколько особенностей и функций.
Первое это отложенная загрузка. Пока вы не будите использовать его, класс не будет загружен.
Второе, вы можете указать зависимость от других сервисов и параметров. Объясню на примере.
Пусть у нас есть класс Bar, который зависит от классов One и Two:
namespace MySpace;
class One {}
class Two {}
class Bar
{
public function __construct(One $one, Two $two)
{
}
}
Опишем эту зависимость в Inversion:
use InversionService;
//...
$container['one'] = 'MySpaceOne';
$container['two'] = 'MySpaceTwo';
$container['bar'] = new Service('MySpaceBar', array($container['one'], $container['two']));
Теперь при вызове «bar», они будут созданы и подставлены в конструктор. На самом деле можно ещё проще. Если вместо «one» и «two» указать их имена классов:
$container['MySpaceOne'] = 'MySpaceOne';
$container['MySpaceTwo'] = 'MySpaceTwo';
$container['MySpaceBar'] = new Service('MySpaceBar'); // "new Service" можно опустить
Это удобный способ описывать зависимости при использовании интерфейсов:
namespace MySpace;
class One implements OneInterface {}
class Two implements TwoInterface {}
class Bar implements BarInterface
{
public function __construct(OneInterface $one, TwoInterface $two)
{
}
}
$container['MySpaceOneInterface'] = 'MySpaceOne';
$container['MySpaceTwoInterface'] = 'MySpaceTwo';
$container['MySpaceBarInterface'] = 'MySpaceBar';
Вообще имена интерфейсов, можно опустить. Они будут автоматически получены из классов:
$container[] = 'MySpaceOne';
$container[] = 'MySpaceTwo';
$container[] = 'MySpaceBar';
Вот так вот просто.
Однако, нужно понимать что в таком случае классы будут сразу же загружены чтобы получить список интерфейсов через рефлексию. Поэтому лучше указывать имя интерфейса вручную.
Другие виды сервисов
В библиотеке идет несколько сервисов, однако вы можете создать свой имплементировав InversionServiceInterface.
Closure
Класс: InversionServiceClosure
Использование:
$container['closure'] = function () use ($container) {
return new MyClass();
};
Можно также указать зависимости:
$container['closure'] = function (One $foo, Two $foo) use ($container) {
return new MyClass();
};
Так же как и с InversionService можно указать их явно:
$container['closure'] = new Closure(function (One $foo, Two $foo) use ($container) {
return new MyClass();
}, array($container['one'], $container['two']));
Factory
Класс: InversionServiceFactory
Использование:
$container['factory'] = new Factory('MyClassFactory', 'create');
Так же можно указать зависимости для конструктора явно третьим параметром.
Object
Класс: InversionServiceObject
Использование:
$container['object'] = new MyClass();
или
$container['object'] = new Object(new MyClass());
Prototype
Класс: InversionServicePrototype
Использование:
$container['prototype'] = new Prototype($object);
При каждом вызове будет создана новая копия: clone $object.
Data
Класс: InversionServiceData
Использование:
$container['data'] = new Data('what you want');
По умолчанию все массивы преобразуется в Data сервисы.
$container['data'] = array(...);
Эквивалентно:
$container['data'] = new Data(array(...));
Ссылки на сервисы
Inversion поддерживает ссылки. Что бы получить ссылку обратитесь к контейнеру как к массиву:
$container['foo'] = new Service(...);
$ref = $container['foo']; // Ссылка на сервис.
Таким образом можно создать алиас к любому сервису:
$container['MyClassFooInterface'] = new Service('MyClassFoo');
$container['foo'] = $container['MyClassFooInterface'];
//...
$foo = $container('foo');
Теперь если кто-нибудь перезапишет «MyClassFooInterface», то «foo» будет по прежнему ссылаться на этот сервис:
//...
$container['MyClassFooInterface'] = new Service('AnotherFooImpl');
//...
$foo = $container('foo'); // $foo instanseof AnotherFooImpl
Можно даже создавать ссылки на ссылки:
$container['foo'] = 'MyClassFoo';
$container['ref'] = $container['foo'];
$container['ref2'] = $container['ref'];
$container['ref3'] = $container['ref2'];
//...
$foo = $container('ref3'); // $foo instanseof MyClassFoo
$name = $container->getRealName('ref3'); // $name == 'foo'
Расширение сервисов
Например если мы хотим расширить какой-нибудь сервис, то такой способ не подойдет т.к. он перезапишет первый:
$container['MyClassFooInterface'] = 'MyClassFoo';
//...
$container['MyClassFooInterface'] = function (FooInterface $foo) {
$foo->extendSome(...);
return $foo;
};
В результате будет зацикливание, что бы этого избежать, для расширения используйте следующую функцию:
$container['MyClassFooInterface'] = 'MyClassFoo';
//...
$container->extend('MyClassFooInterface', function (FooInterface $foo) {
return new FooDecorator($foo);
});
Тесты
Библиотека Inversion полностью тестирована. Тесты находятся в отдельном репозитории (granula/test) для уменьшения размера библиотеки.
Как Singleton
Inversion спроектирована полностью без использования статических методов и синглетонов, однако редко бывает полезно иметь контейнер как синглетон:
$container = InversionContainer::getInstanse();
Другие реализации
- Symfony Dependency Injection — мощная и тяжёлая библиотека внедрения зависимости. Имеет хорошую документацию.
- Pimple — простой и очень лёгкий (всего один файл) «контейнер» от создателя Symfony.
Автор: Elfet