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

от автора

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.

ссылка на оригинал статьи http://habrahabr.ru/post/169187/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *