Продолжение, начало в Части 1
Phemto распростаняется простым тарболлом, так, что просто распакуйте его…
Достаточно использовать require_once(), чтобы включить файл phemto.php
Единственная зависимость Phemto, это механизм PHP reflection.
Phemto лучше всего использовать в главном скрипте или классе приложения или фреймворка.
Сначала вы пишете классы, как обычно…
Обычная архитектура Page controller. Мы можем легко сделать модульный тест для Page, поскольку его зависимость от Authentication передается в конструктор. Мы можем использовать версию-имитацию для теста и сконцентрироваться на логике.
Теперь мы напишем файл с цепочечной конфигурацией, назовем его «wiring.php». В нем содержатся все необходимые настройки для нашего приложения…
Здесь мы говорим нашему инжектору, что если он увидит аргумент $authentication, то нужно создать экземпляр Authentication. Объект для аргумента $permissions имеет другой жизненный цикл. Sessionable говорит о том, что если возможно, надо взять объект из сессии, иначе создать его и сохранить в сессии, так, что объект будет создан лишь однажды.
Наш главный скрипт вместо new теперь использует вызовы фабрик Phemto…
Таким образом, наш код настолько изолирован от главного скрипта, что мы можем добавлять и убирать зависимости между классами, безо всякого вмешательства в главный скрипт.
У авторов фреймворков обычно другая цель. Скорее всего у них уже есть центральная точка для создания страниц, и главная работа, это адаптировать компоненты сторонних разработчиков.
Пусть мы хотим написать реализацию Authentication на основе интерфейса фреймворка…
Наш компонент будет использовать общее с фреймворком подключение к БД, и еще мы хотим взять кэширующий компонент третьей стороны.
Для фреймворка, основанного на фабриках, такой расклад близок к кошмару, поскольку фреймворк не знает, как создать компонент кэша, и куда его деть. Заставить нас передать фреймворку и фабрику, – не выход, поскольку фреймворк все равно должен будет, куда-то выдать и положить кэширующий компонент. Если же фреймворк использует Dependency Injection, то задача сводится всего лишь к настройке цепочки.
Цепочка может быть изменена напрямую с помощью пользовательского файла…
Однако, скорее всего, фреймворк поместит инструмент DI в свою систему регистрации…
И тогда мы можем сделать такой вызов…
Простейший случай создания Phemto объекта, это через имя класса…
Среди зарегистрированных классов будет найден подходящий.
Если только один класс может удовлетворить условию, тогда именно этого класса и будет создан объект. Phemto в этом вопросе достаточно умен и понимает абстрактные классы и интерфейсы…
Здесь $car – экземпляр класса Porsche911. Также и…
Опять будет создан объект класса Porsche911, как единственно возможный вариант.
Если имеет место неясность, то Phemto бросит исключение. Неясность можно разрешить добавив в цепочку дополнительную информацию…
Это удобно и когда надо перекрыть стандартную реализацию, в то время как стандартный класс зарегистрирован в системе.
У Phemto есть два метода автоматического создания параметров. Первый, это с помощью типа…
Это равнозначно new Porsche911(new Flat6()). Такой способ удобен авторам фреймворков, которым достаточно задать лишь имена интерфейсов.
Обратите внимание, – нам не пришлось менять основной код, даже несмотря на то, что мы поменяли сигнатуру конструктора.
Другой способ, – Phemto может создать параметр по имени аргумента…
Опять мы для $car создаем объект класса new Porsche911(new Flat6()). Здесь мы воспользовались именем аргумента $engine, чтобы вычислить интерфейс. И дальше Phemto смог применить свои правила автоматизации.
Иногда все же надо передать параметры конструктору. Простейший способ сделать это, – добавить их в метод create…
Эти параметры займут свои места в конструкторе, в данном случае получится new Porsche911(true, false).
Неименованные параметры могут быть причиной ошибок, когда код станет посложнее, так, что можно воспользоваться и параметрами с именами…
Эти параметры также можно использовать и с зависимостями.
Phemto может вызывать и методы отличные от конструктора…
Этот код аналогичен...
Такой вызов методов, отличных от конструктора называется setter injection.
Далеко не всегда нужно создавать один и тот же объект. Иногда выбор должен определяться контекстом…
Можете быть уверены, – по умолчанию $seat будет объектом класса StandardSeat, но для Porsche911 будет использован BucketSeat.
Метод whenCreating() создаст новую вложенную версию Phemto, так, что контекст действует на все предыдущие настройки в цепочке, т.е…
Жизненный цикл объектов, созданных с помощью Phemto можно контролировать.
Phemto имеет встроенные классы: Factory (по умолчанию), который всегда создает новый экземпляр объекта, Reused который отдает ссылки на один и тот же экземпляр, и Sessionable, который хранит экземпляр объекта в системной переменной PHP $_SESSION. Они все наследуют от базового абстрактного класса Lifecycle. Разработчики могут расширять эти классы…
Здесь мы создадим единственный экземпляр объекта Porsche911 и будем раздавать ссылки
$car и $same_car будут ссылаться на один и тот же объект. В конце концов, Porsche довольно дорогие машинки.
Ссылки и дополнительная информация
Установка Phemto
Phemto распростаняется простым тарболлом, так, что просто распакуйте его…
tar -zxf phemto_0.1_alpha6.tar.gz
Достаточно использовать require_once(), чтобы включить файл phemto.php
Единственная зависимость Phemto, это механизм PHP reflection.
Phemto в Вашей программе
Phemto лучше всего использовать в главном скрипте или классе приложения или фреймворка.
Сначала вы пишете классы, как обычно…
class Permissions { ... }
class Authentication {
function __construct($permissions) { ... }
}
class MyPage implements Page {
function __construct($authentication) { ... }
}
Обычная архитектура Page controller. Мы можем легко сделать модульный тест для Page, поскольку его зависимость от Authentication передается в конструктор. Мы можем использовать версию-имитацию для теста и сконцентрироваться на логике.
Теперь мы напишем файл с цепочечной конфигурацией, назовем его «wiring.php». В нем содержатся все необходимые настройки для нашего приложения…
require_once('phemto/phemto.php');
$injector = new Phemto();
$injector->forVariable('authentication')->willUse('Authentication');
$injector->whenCreating('Authentication')
->forVariable('permissions')->willUse(new Sessionable('Permissions'));
return $injector;
Здесь мы говорим нашему инжектору, что если он увидит аргумент $authentication, то нужно создать экземпляр Authentication. Объект для аргумента $permissions имеет другой жизненный цикл. Sessionable говорит о том, что если возможно, надо взять объект из сессии, иначе создать его и сохранить в сессии, так, что объект будет создан лишь однажды.
Наш главный скрипт вместо new теперь использует вызовы фабрик Phemto…
require_once('lib/my_page.php');
$injector = include('wiring.php');
$page = $injector->create('Page');
?>
<html>...</html>
Таким образом, наш код настолько изолирован от главного скрипта, что мы можем добавлять и убирать зависимости между классами, безо всякого вмешательства в главный скрипт.
У авторов фреймворков обычно другая цель. Скорее всего у них уже есть центральная точка для создания страниц, и главная работа, это адаптировать компоненты сторонних разработчиков.
Пусть мы хотим написать реализацию Authentication на основе интерфейса фреймворка…
interface Authentication { ... }
class InternalFrontControllerActionChainThingy {
function __construct(Authentication $authentication, ...) { ... }
}
Наш компонент будет использовать общее с фреймворком подключение к БД, и еще мы хотим взять кэширующий компонент третьей стороны.
require_once('cache.php');
class OurAuthentication implements Authentication {
function __construct(Database $database, DatabaseCache $cache) { ... }
}
Для фреймворка, основанного на фабриках, такой расклад близок к кошмару, поскольку фреймворк не знает, как создать компонент кэша, и куда его деть. Заставить нас передать фреймворку и фабрику, – не выход, поскольку фреймворк все равно должен будет, куда-то выдать и положить кэширующий компонент. Если же фреймворк использует Dependency Injection, то задача сводится всего лишь к настройке цепочки.
Цепочка может быть изменена напрямую с помощью пользовательского файла…
$injector = include('framework/wiring.php');
$injector->willUse('OurAuthenticator');
return $injector;
Однако, скорее всего, фреймворк поместит инструмент DI в свою систему регистрации…
class FrameworkRegistration {
...
static function register($class, $dependencies = array()) {
$this->injector->whenCreating('Controller')->willUse($class);
foreach (dependencies as $dependency) {
$this->injector->whenCreating('Controller')
->whenCreating($class)
->willUse($dependency);
}
}
}
И тогда мы можем сделать такой вызов…
FrameworkRegistration::register('OurAuthentication', array('DatabaseCache'));
Цепочечный синтаксис Phemto
Простейший случай создания Phemto объекта, это через имя класса…
class Porsche911 { }
$injector = new Phemto();
$car = $injector->create('Porsche911');
Среди зарегистрированных классов будет найден подходящий.
Если только один класс может удовлетворить условию, тогда именно этого класса и будет создан объект. Phemto в этом вопросе достаточно умен и понимает абстрактные классы и интерфейсы…
abstract class Car { }
class Porsche911 extends Car { }
$injector = new Phemto();
$car = $injector->create('Car');
Здесь $car – экземпляр класса Porsche911. Также и…
interface Transport { }
class Porsche911 implements Transport { }
$injector = new Phemto();
$car = $injector->create('Transport');
Опять будет создан объект класса Porsche911, как единственно возможный вариант.
Если имеет место неясность, то Phemto бросит исключение. Неясность можно разрешить добавив в цепочку дополнительную информацию…
interface Transport { }
class Porsche911 implements Transport { }
class RouteMaster implements Transport { }
$injector = new Phemto();
$injector->willUse('Porsche911');
$car = $injector->create('Transport');
Это удобно и когда надо перекрыть стандартную реализацию, в то время как стандартный класс зарегистрирован в системе.
У Phemto есть два метода автоматического создания параметров. Первый, это с помощью типа…
interface Engine { }
class Porsche911 {
function __construct(Engine $engine) { }
}
class Flat6 implements Engine { }
$injector = new Phemto();
$car = $injector->create('Porsche911');
Это равнозначно new Porsche911(new Flat6()). Такой способ удобен авторам фреймворков, которым достаточно задать лишь имена интерфейсов.
Обратите внимание, – нам не пришлось менять основной код, даже несмотря на то, что мы поменяли сигнатуру конструктора.
Другой способ, – Phemto может создать параметр по имени аргумента…
class Porsche911 {
function __construct($engine) { }
}
interface Engine { }
class Flat6 implements Engine { }
$injector = new Phemto();
$injector->forVariable('engine')->willUse('Engine');
$car = $injector->create('Porsche911');
Опять мы для $car создаем объект класса new Porsche911(new Flat6()). Здесь мы воспользовались именем аргумента $engine, чтобы вычислить интерфейс. И дальше Phemto смог применить свои правила автоматизации.
Иногда все же надо передать параметры конструктору. Простейший способ сделать это, – добавить их в метод create…
class Porsche911 {
function __construct($fluffy_dice, $nodding_dog) { }
}
$injector = new Phemto();
$car = $injector->create('Porsche911', true, false);
Эти параметры займут свои места в конструкторе, в данном случае получится new Porsche911(true, false).
Неименованные параметры могут быть причиной ошибок, когда код станет посложнее, так, что можно воспользоваться и параметрами с именами…
class Porsche911 {
function __construct($fluffy_dice, $nodding_dog) { }
}
$injector = new Phemto();
$car = $injector->fill('fluffy_dice', 'nodding_dog')
->with(true, false)
->create('Porsche911', true);
Эти параметры также можно использовать и с зависимостями.
Phemto может вызывать и методы отличные от конструктора…
interface Seat { }
interface SportsCar { }
class Porsche911 implements SportsCar {
function fitDriversSeat(Seat $seat) { }
}
class BucketSeat implements Seat { }
$injector = new Phemto();
$injector->forType('SportsCar')->call('fitDriversSeat');
$car = $injector->create('Porsche911');
Этот код аналогичен...
$car = new Porsche911();
$car->fitDriversSeat(new BucketSeat());
Такой вызов методов, отличных от конструктора называется setter injection.
Далеко не всегда нужно создавать один и тот же объект. Иногда выбор должен определяться контекстом…
interface Seat { }
class Car {
function __construct(Seat $seat) { }
}
class FordEscort extends Car;
class Porsche911 extends Car;
class StandardSeat implements Seat { }
class BucketSeat implements Seat { }
$injector = new Phemto();
$injector->willUse('StandardSeat');
$injector->whenCreating('Porsche911')->willUse('BucketSeat');
$car = $injector->create('Porsche911');
Можете быть уверены, – по умолчанию $seat будет объектом класса StandardSeat, но для Porsche911 будет использован BucketSeat.
Метод whenCreating() создаст новую вложенную версию Phemto, так, что контекст действует на все предыдущие настройки в цепочке, т.е…
class Car {
function __construct($seat) { }
}
class FordEscort extends Car;
class Porsche911 extends Car;
class StandardSeat { }
class BucketSeat { }
$injector = new Phemto();
$injector->willUse('StandardSeat');
$injector->whenCreating('Porsche911')
->forVariable('seat')->willUse('BucketSeat');
$car = $injector->create('Porsche911');
Жизненный цикл объектов, созданных с помощью Phemto можно контролировать.
Phemto имеет встроенные классы: Factory (по умолчанию), который всегда создает новый экземпляр объекта, Reused который отдает ссылки на один и тот же экземпляр, и Sessionable, который хранит экземпляр объекта в системной переменной PHP $_SESSION. Они все наследуют от базового абстрактного класса Lifecycle. Разработчики могут расширять эти классы…
Здесь мы создадим единственный экземпляр объекта Porsche911 и будем раздавать ссылки
class Porsche911 { }
$injector = new Phemto();
$injector->willUse(new Reused('Porsche911'));
$car = $injector->create('Porsche911');
$same_car = $injector->create('Porsche911');
$car и $same_car будут ссылаться на один и тот же объект. В конце концов, Porsche довольно дорогие машинки.
Ссылки и дополнительная информация
- Страница Phemto на SourceForge.
- Статья Мартина Фаулера description of DI.