Pull to refresh

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

Reading time 4 min
Views 7.4K
Inversion это простой и функциональный контейнер внедрения зависимости для PHP 5.3. Поддерживает сервис-ориентированную архитектуру, ссылки, PRS-0, и Composer.



Установить можно через packagist.org: granula/inversion либо скачав и добавив к PRS-0 совместимому загрузчику.

$container = new Inversion\Container();
$container['foo'] = 'My\Class\Foo';
// ...
$foo = $container('foo');


В вышеприведённом примере показана базовая функциональность контейнера. Разберем что там происходит.
В первой строчки создаем экземпляр контейнера. Во второй создаем ассоциацию между «foo» и сервисом создающим экземпляр класса «My\Class\Foo». Что по другому можно записать так:
$container->addService(new Service('My\Class\Foo'), 'foo');

Имя «foo» идёт вторым, т.к. его вообще можно опустить. Подробнее ниже.

В третей строчке мы получаем экземпляр объекта. Что по другому можно записать так:
$foo = $container('foo');
// или
$foo = $container->get('foo');
// или
$foo = $container['foo']->get();
// или 
$foo = $container->getService('foo')->get();

Однако, рекомендую использовать сокращённый вариант, хотя все они допустимы.


Описание зависимостей


По умолчанию когда в контейнер передаётся строка она понимается как имя класса и подставляется в сервис Inversion\Servise.
У данного сервиса есть несколько особенностей и функций.
Первое это отложенная загрузка. Пока вы не будете использовать его, класс не будет загружен.
Второе, вы можете указать зависимость от других сервисов и параметров. Объясню на примере.
Пусть у нас есть класс Bar, который зависит от классов One и Two:
namespace My\Space;
class One {}
class Two {}
class Bar
{
    public function __construct(One $one, Two $two) 
    {
    }
}

Опишем эту зависимость в Inversion:
use Inversion\Service;
//...
$container['one'] = 'My\Space\One';
$container['two'] = 'My\Space\Two';
$container['bar'] = new Service('My\Space\Bar', array($container['one'], $container['two']));

Теперь при вызове «bar», они будут созданы и подставлены в конструктор. На самом деле можно ещё проще. Если вместо «one» и «two» указать их имена классов:
$container['My\Space\One'] = 'My\Space\One';
$container['My\Space\Two'] = 'My\Space\Two';
$container['My\Space\Bar'] = new Service('My\Space\Bar'); // "new Service" можно опустить 

Это удобный способ описывать зависимости при использовании интерфейсов:
namespace My\Space;
class One implements OneInterface {}
class Two implements TwoInterface  {}
class Bar implements BarInterface 
{
    public function __construct(OneInterface $one, TwoInterface $two) 
    {
    }
}


$container['My\Space\OneInterface'] = 'My\Space\One';
$container['My\Space\TwoInterface'] = 'My\Space\Two';
$container['My\Space\BarInterface'] = 'My\Space\Bar'; 

Вообще имена интерфейсов, можно опустить. Они будут автоматически получены из классов:
$container[] = 'My\Space\One';
$container[] = 'My\Space\Two';
$container[] = 'My\Space\Bar'; 

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


Другие виды сервисов


В библиотеке идет несколько сервисов, однако вы можете создать свой имплементировав Inversion\ServiceInterface.

Closure


Класс: Inversion\Service\Closure
Использование:
$container['closure'] = function () use ($container) {
    return new My\Class();
};

Можно также указать зависимости:
$container['closure'] = function (One $foo, Two $foo) use ($container) {
    return new My\Class();
};

Так же как и с Inversion\Service можно указать их явно:
$container['closure'] = new Closure(function (One $foo, Two $foo) use ($container) {
    return new My\Class();
}, array($container['one'], $container['two']));


Factory


Класс: Inversion\Service\Factory
Использование:
$container['factory'] = new Factory('My\ClassFactory', 'create');

Так же можно указать зависимости для конструктора явно третьим параметром.

Object


Класс: Inversion\Service\Object
Использование:
$container['object'] = new My\Class();

или
$container['object'] = new Object(new My\Class());


Prototype


Класс: Inversion\Service\Prototype
Использование:
$container['prototype'] = new Prototype($object);

При каждом вызове будет создана новая копия: clone $object.

Data


Класс: Inversion\Service\Data
Использование:
$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['My\Class\FooInterface'] = new Service('My\Class\Foo');
$container['foo'] = $container['My\Class\FooInterface']; 
//...
$foo = $container('foo');

Теперь если кто-нибудь перезапишет «My\Class\FooInterface», то «foo» будет по прежнему ссылаться на этот сервис:
//...
$container['My\Class\FooInterface'] = new Service('Another\FooImpl');
//...
$foo = $container('foo'); // $foo instanseof Another\FooImpl

Можно даже создавать ссылки на ссылки:
$container['foo'] = 'My\Class\Foo';
$container['ref'] = $container['foo']; 
$container['ref2'] = $container['ref']; 
$container['ref3'] = $container['ref2'];
//...
$foo = $container('ref3'); // $foo instanseof My\Class\Foo
$name = $container->getRealName('ref3'); // $name == 'foo'

Расширение сервисов


Например если мы хотим расширить какой-нибудь сервис, то такой способ не подойдет т.к. он перезапишет первый:
$container['My\Class\FooInterface'] = 'My\Class\Foo';
//...
$container['My\Class\FooInterface'] = function (FooInterface $foo) {
    $foo->extendSome(...);
    return $foo;
};

В результате будет зацикливание, что бы этого избежать, для расширения используйте следующую функцию:
$container['My\Class\FooInterface'] = 'My\Class\Foo';
//...
$container->extend('My\Class\FooInterface', function (FooInterface $foo) {
    return new FooDecorator($foo);
});

Тесты


Библиотека Inversion полностью тестирована. Тесты находятся в отдельном репозитории (granula/test) для уменьшения размера библиотеки.

Как Singleton


Inversion спроектирована полностью без использования статических методов и синглетонов, однако редко бывает полезно иметь контейнер как синглетон:
$container = Inversion\Container::getInstanse();


Другие реализации


  • Symfony Dependency Injection — мощная и тяжёлая библиотека внедрения зависимости. Имеет хорошую документацию.
  • Pimple — простой и очень лёгкий (всего один файл) «контейнер» от создателя Symfony.
Tags:
Hubs:
+1
Comments 18
Comments Comments 18

Articles