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

в 17:59, , рубрики: dependency injection, inversion of control, php, метки: , ,

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

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

Установить можно через 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

Источник

Поделиться

* - обязательные к заполнению поля