При разработке приложений мы стараемся создавать независимые модули, чтобы мы могли повторно использовать код в будущих проектах. Но сложно создать полностью независимые модули, которые предоставляют полезную функциональность; их зависимости могут вызвать кошмары обслуживания, если они не управляются должным образом. Именно здесь Dependency Injection оказывается полезным, поскольку дает нам возможность внедрить зависимости, которые необходимы нашему коду для правильной работы, без жесткого кодирования их в модули.
Pimple — это простой контейнер внедрения зависимостей, который использует закрытие PHP для определения зависимостей управляемым способом. В этой статье мы рассмотрим проблемы с жестким кодированием зависимостей, то, как их решает инъекция зависимостей, и как использовать Pimple, чтобы сделать код, который использует преимущества внедрения зависимостей, более понятным.
Проблемы с конкретными зависимостями
Мы используем ряд классов PHP при написании приложений. Один класс может нуждаться в вызове методов одного или нескольких других классов для обеспечения предполагаемой функциональности, и поэтому мы говорим, что первый класс зависит от других классов. Например:
<?php
class A
{
public function a1() {
$b = new B();
$b->b1();
}
}
Класс A
B
Если класс B
Более того, каждый раз, когда мы жестко программируем создание объекта внутри класса, мы делаем конкретную зависимость от этого класса. Конкретные зависимости являются препятствием для написания тестируемого кода. Лучший способ — предоставить объект класса B
A
Эти объекты могут быть предоставлены через конструктор A
Давайте посмотрим на более реалистичный сценарий, прежде чем идти дальше.
Обмен контентом на сайтах социальных сетей очень распространен в наши дни, и большинство сайтов отображают свои каналы социальных сетей прямо на своем сайте. Предположим, у нас есть класс SocialFeeds
Здесь мы рассмотрим класс, который взаимодействует с Twitter, TwitterService
Класс SocialFeeds
TwitterService
TwitterService
Токен передается в класс OAuth
SocialFeeds
<?php
class SocialFeeds
{
public function getSocialFeeds() {
$twService = new TwitterService();
echo $twService->getTweets();
}
}
<?php
class TwitterService
{
public function getTweets() {
$db = new DB();
$query = "Query to get user token from database";
$token = $db->getQueryResults($query);
$oauth = new OAuth();
return $oauth->requestTwitterFeed($token);
}
}
<?php
class OAuth
{
public function requestTwitterFeed($token) {
// Retrieve and return twitter feed using the token
}
}
<?php
class DB
{
public function getQueryResults($query) {
// Get results from database and return token
}
}
Понятно, что SocialFeeds
TwitterService
Но TwitterService
DB
OAuth
SocialFeeds
DB
OAuth
Так в чем проблемы? SocialFeeds
SocialFeeds
Или, скажем, мы хотим использовать другую базу данных или другого поставщика OAuth. В этом случае мы должны были бы заменить существующие классы новым классом в каждом случае в нашем коде.
Исправление конкретных зависимостей
Решение этих проблем с зависимостями так же просто, как динамическое предоставление объектов при необходимости без использования конкретных реализаций. Существует два типа методов внедрения зависимостей: внедрение на основе конструктора и внедрение на основе установщика.
Конструктор на основе инъекций
При внедрении зависимостей на основе конструктора зависимые объекты создаются извне и передаются конструктору класса в качестве параметров. Мы можем назначить эти объекты переменным класса и использовать в любом месте внутри класса. Инжектор на основе SocialFeeds
<?php
class SocialFeeds
{
public $twService;
public function __construct($twService) {
$this->twService = $twService;
}
public function getSocialFeeds() {
echo $this->twService->getTweets();
}
}
TwitterService
Экземпляр SocialFeeds
TwitterService
DB
Классы OAuth
TwitterService
<?php
$db = new DB();
$oauth = new OAuth();
$twService = new TwitterService($db, $oauth);
$socialFeeds = new SocialFeeds($twService);
$socialFeeds->getSocialFeeds();
SocialFeeds
Сеттер-инъекция
При внедрении на основе сеттера объекты предоставляются через методы сеттера вместо конструктора. Вот основанная на установщике реализация внедрения зависимости для класса <?php
class SocialFeeds
{
public $twService;
public function getSocialFeeds() {
echo $this->twService->getTweets();
}
public function setTwitterService($twService) {
$this->twService = $twService;
}
}
DB
Код инициализации, включая OAuth
<?php
$db = new DB();
$oauth = new OAuth();
$twService = new TwitterService();
$twService->setDB($db);
$twService->setOAuth($oauth);
$socialFeeds = new SocialFeed();
$socialFeeds->setTwitterService($twService);
$socialFeeds->getSocialFeeds();
SocialFeed
Конструктор против инъекции сеттера
Вам остается выбирать между инжектором на основе конструктора или сеттера. Инъекции на основе конструктора подходят, когда требуются все зависимости для создания экземпляра класса. Инъекции на основе сеттера подходят, когда зависимости не требуются в каждом случае.
преимущества
- Конструктор — все зависимости класса можно идентифицировать, просто взглянув на конструктор класса
- Setter — добавить новую зависимость так же просто, как добавить новый метод setter, который не нарушает существующий код
Недостатки
- Конструктор — добавление новой зависимости увеличивает параметры конструктора; существующий код должен быть обновлен во всем нашем приложении, чтобы обеспечить новую зависимость
- Сеттер — мы должны вручную искать необходимые зависимости, так как они нигде не указаны
Обладая знаниями о внедрении зависимостей и различных методах внедрения, пришло время взглянуть на Pimple и посмотреть, как он вписывается.
Роль прыщей в DI
Вам может быть интересно, почему Pimple необходим, когда мы можем внедрить зависимости, уже используя методы, упомянутые ранее. Чтобы ответить на этот вопрос, нам нужно обратиться к принципу СУХОЙ.
«Не повторяйся сам» (DRY) — это принцип разработки программного обеспечения, направленный на сокращение повторения информации всех видов, особенно полезный в многоуровневых архитектурах. Принцип СУХОЙ гласит: «Каждое знание должно иметь одно, однозначное, авторитетное представление в системе — Википедия
Рассмотрим пример внедрения на основе конструктора. Каждый раз, когда мы хотим получить объект класса <?php
$container = new Pimple();
$container['class_name'] = 'Test';
$container['object_name'] = function ($c) {
return new $c['class_name']();
};
$testClass = $container['object_name']; Согласно DRY, такого кода следует избегать, чтобы избежать головной боли от обслуживания. Pimple действует как контейнер для определения таких зависимостей, чтобы избежать повторения.
Давайте посмотрим на простой пример, чтобы увидеть, как работает Pimple.
Pimple
Экземпляр ArrayAccess
Он реализует интерфейс SPL $c
Сначала мы определили ключ, который содержит имя некоторого произвольного класса, который мы хотим. Затем мы определили замыкание для возврата экземпляра указанного класса, который действует как служба. Обратите внимание, что $c
каждый определенный параметр или объект доступен в замыкании через переменную SocialFeeds
Теперь, когда мы хотим получить экземпляр класса, мы можем ссылаться на ключ, чтобы получить объект.
Давайте преобразуем пример SocialFeeds в Pimple. Примеры на официальном сайте Pimple показывают инъекцию на основе конструктора, поэтому здесь мы проиллюстрируем инъекцию на основе сеттера. Имейте в виду, что любые методы или код сеттера, которые мы определили ранее, не нуждаются в модификации, чтобы мы могли использовать Pimple — мы просто инкапсулируем логику.
<?php
$container = new Pimple();
$container['oauth'] = function($c) {
return new OAuth();
};
$container['db'] = function($c) {
return new DB();
};
$container['tweet_service'] = function($c) {
$twService = new TwitterService();
$twService->setDB($c['db']);
$twService->setOauth($c['oauth']);
return $twService;
};
$container['social_feeds'] = function($c) {
$socialFeeds = new SocialFeeds();
$socialFeeds->setTwitterService($c['tweet_service']);
return $socialFeeds;
};
$socialFeeds = $container['social_feeds'];
$socialFeeds->getSocialFeeds();
Классы DB
OAuth
Затем мы добавляем зависимости в класс TwitterService
Мы уже добавили классы DB
OAuth
$c['db']
$c['oauth']
Теперь зависимости инкапсулированы внутри контейнера как сервисы. Всякий раз, когда мы хотим использовать другой класс DB
OAuth
С Pimple вам нужно добавлять новые зависимости только в одном месте.
Расширенное использование прыщ
В приведенном выше сценарии Pimple будет возвращать новые экземпляры каждого класса из замыкания при каждом запросе. Есть определенные сценарии, когда нам нужно использовать один и тот же объект без инициализации новых экземпляров каждый раз, например, подключение к базе данных является идеальным примером.
Pimple предоставляет возможность возвращать один и тот же экземпляр, используя объекты общего доступа, для этого необходимо, чтобы мы указали замыкание с помощью метода share()
<?php
$container['db'] = $container->share(function ($c) {
return new DB();
});
Кроме того, до сих пор мы определили все наши зависимости в одном месте внутри контейнера Pimple. Но представьте себе ситуацию, когда нам нужны сервисы с их зависимостями, но настроенные немного иначе, чем оригинал. Например, допустим, нам нужен доступ к ORM для определенных функций класса TwitterService
Мы не можем изменить существующее замыкание, поскольку оно заставит все существующие функции использовать ORM.
Pimple предоставляет метод extend()
Рассмотрим следующий код:
<?php
$container['tweet_service'] = $container->extend('tweet_service', function($twSservice, $c) {
$twService->setDB(new ORM());
return $twService;
});
Теперь мы можем использовать разные расширенные версии tweet_service
Первый параметр — это имя службы, второй — функция, которая получает доступ к экземпляру объекта и контейнеру.
Действительно, extend()
Резюме
Управление зависимостями является одной из наиболее важных и сложных задач в разработке веб-приложений. Мы можем использовать Dependency Injection, используя конструкторы и методы установки, чтобы эффективно управлять ими. Впрочем, внедрение зависимостей имеет свои собственные проблемы, которые Pimple решает, предоставляя облегченный контейнер для создания и хранения объектных зависимостей в СУХОЙ манере.
Не стесняйтесь делиться своим опытом управления зависимостями в своих проектах и тем, что вы думаете о Pimple как контейнере внедрения зависимостей, в комментариях ниже.
Изображение через Fotolia