Паттерн dependency injection php

Phemto и Паттерн Dependency Injection. Часть 1

Я не встречал хорошего описания паттерна Dependency Injection применительно к PHP.

Недавно ребята из Symfony выпустили свой контейнер DI, снабдив его подробной и хорошей книжкой о том как работать с этим паттерном.

Я вспомнил еще об одной библиотеке для DI, Phemto. Ее автор, — Маркус Бэйкер, создатель SimpleTest. К сожалению на сайте содержится краткая и невнятная справка. тем не менее, проект развиавется, а внутри дистрибутива лежит статья с крайне хорошим объяснением про DI, ну и руководством конечно. Phemto, — очень миниатюрный проект, состоящий из трех не очень больших файлов.

Мне показалось, полезным перевести статью на русский язык и выложить сюда. Статья не очень большая, но содержательная. Ссылку на оригинал дать не могу, оригинал внутри дистрибутива 🙂

На программистском жаргоне, Phemto – это легкий, автоматизированный контейнер dependency injection (управления зависимостями). Проще говоря, задача Phemto – создавать экземпляр объекта, получая минимум информации, таким образом, значительно ослабляя зависимости внутри приложения или фреймворка.

Проще всего понять паттерн DI это представить себе шкалу с «Используем DI» на одном конце и «Используем хардкодинг (т.е. жестко запрограммированные связи)» на другом. Мы с вами сейчас устроим маленькое путешествие от хардкодинга через паттерны Factory, Registry, Service Locator к DI. Если Вы и так знаете, что такое DI, переходите сразу к
установке Phemto.

Заурядное создание объектов с помощью оператора new выглядит простым и понятным, но мы, скорее всего, столкнемся с трудностями, когда захотим что-то поменять потом. Посмотрим на код…

Читайте также:  Oracle netbeans with java

Здесь MyController зависит от MysqlConnection.

Оператор new ясен и понятен, но MyController сможет использовать только БД MySQL. Немного переделать класс, чтобы было можно его наследовать едва ли поможет, т.к. тогда мы будем иметь в наследнике вместе с логикой дочернего контроллера и логику получения драйвера БД. В любом случае множественные зависимости не решаются наследованием, приводя к захламлению класса. Вообще говоря, Вы можете разыграть карту наследования только однажды.

Следующий шаг, – используем Factory

class MyController <
function __construct ( $connection_pool ) <
.
$connection = $connection_pool -> getConnection ( ) ;
>
>

Очень эффективное решение. Фабрика может быть настроена на нужный тип драйвера с помощью конфигурационного файла или явно. Фабрики часто могут создавать объекты из разных семейств объектов, и тогда их называют Abstract Factory (Абстрактная Фаброика) или Repository (Репозиторий). Однако тут есть ограничения.

Фабрики приносят много дополнительного кода. Если надо тестировать классы с помощью mock-объектов, то придется имитировать не только сами, возвращаемые фабрикой объекты, но и саму фабрику. Получаете немного дополнительной суеты.

Да и в живом коде, если нужно вернуть объект, о котором автор фабрики не подумал, то придется наследовать или переписывать и саму фабрику, что для фреймворков может оказаться заметной проблемой.

Следующий ход в нашей борьбе с зависимостями, это вообще вынуть создание объекта Registry из основного объекта наружу…

class MyController <
function __construct ( $registry ) <
.
$connection = $registry -> connection ;
>
>
.
$registry = new Registry ( ) ;
$registry -> connection = new MysqlConnection ( ) ;
.
$controller = new MyController ( $registry ) ;

Registry совсем пассивен, зато в основном коде мы создаем и перегружаем много объектов. Мы даже можем случайно насоздавать про запас объектов, которые никогда не потребуются и так и оставить это место.

Кроме того, с помощью такого подхода мы не сможем использовать ленивое создание объектов (lazy loading). Неудача ждет нас, и если мы захотим, чтобы нам возвращался не один и тот же объект адаптера к БД, а разные объекты.

Жизнь сразу ухудшится, если в нашем примере будут еще зависимости, которые надо учесть. Т.е. если, например, для создания объекта-адаптера недостаточно сделать new, а нужно добавить в конструктор какой-то еще объект. В общем, предварительная настройка грозит сделаться весьма запутанной.

Мы можем сделать паттерн Registry более изощренным, если позволим объекту Registry самостоятельно создавать экземпляры нужных объектов. Наш объект стал Сервис-локатором (Service Locator)…

class MyController <
function __construct ( $services ) <
.
$connection = $services -> connection ;
>
>
.
$services = new ServiceLocator ( ) ;
$services -> connection ( ‘MysqlConnection’ ) ;
.
$controller = new MyController ( $services ) ;

Теперь настройки, могут быть в любом порядке, однако ServiceLocator должен знать, как создать MysqlConnection. Задача решается с помощью фабрик или с помощью трюков с рефлексией, хотя передача параметров, может стать весьма кропотливой работой. Жизненный цикл объектов (напр. возвращать один и тот же объект, или создавать разные) теперь под контролем программиста, который может как, запрограммировать все в методах фабрики, так и вынести все в настройки или плагины.

К сожалению, эта почти серебряная пуля имеет ту же проблему, что и Registry. Любой класс, который будет пользоваться таким интерфейсом, неизбежно будет зависеть от Сервис-локатора. Если Вы попробуете смешать две системы с разными сервис-локаторами, вы почувствуете что такое «не повезло».

Dependency Injection заходит немного с другой стороны. Посмотрим на наш самый первый пример…

… и сделаем зависимость внешней.

На первый взгляд, это просто ужасно. Теперь ведь каждый раз в скрипте придется все эти зависимости руками трогать. Изменить адаптер к БД придется вносить изменения в сотне мест. Так бы оно и было, если бы мы использовали new

Хотите верьте, хотите нет, но это все, что нам нужно.

Задача Phemto – выявление того, как создать объект, что позволяет на удивление здорово автоматизировать разработку. Только по типу параметра в интерфейсе он выведет, что MysqlConnection – единственный кандидат, удовлетворяющий нужному типу Connection.

Более сложные ситуации, могут потребовать дополнительной информации, которая обычно содержится в «цепочечном» файле. Вот пример такого файла из реальной жизни, чтобы можно было почувствовать мощь паттерна…

$injector = new Phemto ( ) ;
$injector -> whenCreating ( ‘Page’ ) -> forVariable ( ‘session’ ) -> willUse ( new Reused ( ‘Session’ ) ) ;
$injector -> whenCreating ( ‘Page’ ) -> forVariable ( ‘continuation’ ) -> willUse ( ‘Continuation’ ) ;
$injector -> whenCreating ( ‘Page’ ) -> forVariable ( ‘alerts’ ) -> willUse ( ‘Alert’ ) ;
$injector -> whenCreating ( ‘Page’ ) -> forVariable ( ‘accounts’ ) -> willUse ( ‘Accounts’ ) ;
$injector -> whenCreating ( ‘Page’ ) -> forVariable ( ‘mailer’ ) -> willUse ( ‘Mailer’ ) ;
$injector -> whenCreating ( ‘Page’ ) -> forVariable ( ‘clock’ ) -> willUse ( ‘Clock’ ) ;
$injector -> whenCreating ( ‘Page’ ) -> forVariable ( ‘request’ ) -> willUse ( ‘Request’ ) ;
return $injector ;

Такое количество настроек типично для проекта среднего размера.

Теперь контроллер задает только интерфейс, а работа по созданию объектов выполняется посредником.
MyController теперь не должен вообще знать про MysqlConnection.
Зато $injector знает и о том и о другом. Это называется обращение контроля Inversion of Control.

Источник

Understanding Dependency Injection

Dependency injection and dependency injection containers are different things:

  • dependency injection is a method for writing better code
  • a container is a tool to help injecting dependencies

You don’t need a container to do dependency injection. However a container can help you.

PHP-DI is about this: making dependency injection more practical.

The theory

Classic PHP code

Here is how a code not using DI will roughly work:

  • Application needs Foo (e.g. a controller), so:
  • Application creates Foo
  • Application calls Foo
    • Foo needs Bar (e.g. a service), so:
    • Foo creates Bar
    • Foo calls Bar
      • Bar needs Bim (a service, a repository, …), so:
      • Bar creates Bim
      • Bar does something

      Using dependency injection

      Here is how a code using DI will roughly work:

      • Application needs Foo, which needs Bar, which needs Bim, so:
      • Application creates Bim
      • Application creates Bar and gives it Bim
      • Application creates Foo and gives it Bar
      • Application calls Foo
        • Foo calls Bar
          • Bar does something

          This is the pattern of Inversion of Control. The control of the dependencies is inverted from one being called to the one calling.

          The main advantage: the one at the top of the caller chain is always you. You can control all dependencies and have complete control over how your application works. You can replace a dependency by another (one you made for example).

          For example what if Library X uses Logger Y and you want to make it use your logger Z? With dependency injection, you don’t have to change the code of Library X.

          Using a container

          Now how does a code using PHP-DI works:

          • Application needs Foo so:
          • Application gets Foo from the Container, so:
            • Container creates Bim
            • Container creates Bar and gives it Bim
            • Container creates Foo and gives it Bar
            • Foo calls Bar
              • Bar does something

              In short, the container takes away all the work of creating and injecting dependencies.

              Understanding with an example

              This is a real life example comparing a classic implementation (using new or singletons) VS using dependency injection.

              Without dependency injection

              class GoogleMaps < public function getCoordinatesFromAddress($address) < // calls Google Maps webservice >> class OpenStreetMap < public function getCoordinatesFromAddress($address) < // calls OpenStreetMap webservice >>

              The classic way of doing things is:

              class StoreService < public function getStoreCoordinates($store) < $geolocationService = new GoogleMaps(); // or $geolocationService = GoogleMaps::getInstance() if you use singletons return $geolocationService->getCoordinatesFromAddress($store->getAddress()); > >

              Now we want to use the OpenStreetMap instead of GoogleMaps , how do we do? We have to change the code of StoreService , and all the other classes that use GoogleMaps .

              Without dependency injection, your classes are tightly coupled to their dependencies.

              With dependency injection

              The StoreService now uses dependency injection:

              class StoreService < private $geolocationService; public function __construct(GeolocationService $geolocationService) < $this->geolocationService = $geolocationService; > public function getStoreCoordinates($store) < return $this->geolocationService->getCoordinatesFromAddress($store->getAddress()); > >

              And the services are defined using an interface:

              interface GeolocationService < public function getCoordinatesFromAddress($address); >class GoogleMaps implements GeolocationService < . class OpenStreetMap implements GeolocationService < . 

              Now, it is for the user of the StoreService to decide which implementation to use. And it can be changed anytime, without having to rewrite the StoreService .

              The StoreService is no longer tightly coupled to its dependency.

              With PHP-DI

              You may see that dependency injection has one drawback: you now have to handle injecting dependencies.

              That's where a container, and specifically PHP-DI, can help you.

              $geolocationService = new GoogleMaps(); $storeService = new StoreService($geolocationService);
              $storeService = $container->get('StoreService');

              and configure which GeolocationService PHP-DI should automatically inject in StoreService through configuration:

              $container->set('StoreService', \DI\create('GoogleMaps'));

              If you change your mind, there's just one line of configuration to change now.

              Interested? Go ahead and read the Getting started guide!

              A question? Unsatisfied with the documentation? Open an issue or chat on Gitter or Twitter.

              Want to support my work? Check out Serverless Visually Explained.

              Источник

Оцените статью