Константин

Если кратко DI-контейнер создвет зависимости между классами через рефлексию.
Есть два способа:
1 Собираем или "компилируем" все зависимости в отдельные классы и работаем через них.
2 В моменте запросов к классу через рефлексию находим зависимости и подставляем, этот подход более долгий и главное не явный. Для ускорения используют кеширование, автозамены, синглтоны и т.д., но это не особо помогает при большой вложенности.
Поэтому я выберу первый вариант для демонстрации работы и покажу на примере DI-контейнера на Framework Foton — там это система внедрения зависимостей с автоматическим разрешением аргументов конструктора через рефлексию. То есть он собирает не прям все методы и проверяет их зависимости а только конструкторы если у них они есть. Контейнер сканирует модули в каталоге dev/modules, анализирует их конструкторы, строит дерево зависимостей и генерирует готовые фасадные классы с уже разрешёнными зависимостями.
Для понимания работы этой системы выполните следующие действия по шагам:
Создание модуля
Модули размещаются в dev/modules/. Каждый модуль — класс в неймспейсе Modules.
<?php
// dev/modules/TestDI.php
namespace Modules;
class TestDI
{
public $fileLoad;
public function __construct(FileLoad $object)
{
$this->fileLoad = $object;
}
public function test($text)
{
return 'Class Load:' . get_class($this->fileLoad) . ' Text: ' . $text;
}
}
Если конструктор принимает объект с type-hint, контейнер автоматически найдёт этот класс и рекурсивно разрешит его собственные зависимости.
Конфигурация релизов
Файл dev/facade.php содержит класс Dev\Facade. Каждый метод — это «релиз»: именованная конфигурация сборки модуля.
<?php
namespace Dev;
class Facade
{
private $core;
public function __construct()
{
$this->core = new \Foton\Core();
}
public function realise20($arg = [])
{
return [
'Test' => ['class' => 'Modules\TestDI', 'arg' => []],
];
}
public function realise12($arg = [])
{
return [
'Test' => ['class' => 'Modules\TestDI', 'arg' => []],
];
}
}
Структура возвращаемого массива:
'ИмяФасада' => [
'class' => 'ModulesИмяКласса', // класс модуля
'arg' => ['paramName' => value], // переопределение аргументов конструктора
]
Ключ 'Test' — имя, по которому модуль будет доступен через $this->core->obj('Test')
'class' — полное имя класса модуля
'arg' — ассоциативный массив переопределяемых аргументов конструктора (имя параметра → значение). Если аргумент не указан и не является объектом, генерируется предупреждение
Запуск генерации Из командной строки введите
Базовая генерация фасадов
php foton di autoload
Генерация + вывод всех найденных модулей с зависимостями
php foton di autoload load
Генерация + вывод ошибок
php foton di autoload error
Генерация + вывод всех созданных фасадов
php foton di autoload facade
Использование
После генерации модуль доступен через ядро:
$result = $this->core->obj('Test')->realise20()->test('text');
// Вывод: Class Load:\Modules\FileLoad Text: text
Цепочка вызовов: obj('Test') возвращает сгенерированный фасад Dev\DI\Facades\Test, метод realise20() создаёт экземпляр \Modules\TestDI со всеми зависимостями и возвращает его, test('text') — метод самого модуля.
Посмотрите что у вас получилось, готовый файл класса со всеми зависимостями попадет в dev/di/facades/test.php
<?php
class Test
{
public $propertyClass;
public function realise20($arg = [])
{
// Сначала создаются зависимости (в обратном порядке)
$this->propertyClass["Class_Foton_Core"] = new \Foton\Core();
$this->propertyClass["Class_Modules_FileLoad"] = new \Modules\FileLoad(
$this->propertyClass["Class_Foton_Core"]
);
// Аргументы можно переопределить через $arg
if (count($arg) > 0) {
$arg = array_replace([$this->propertyClass["Class_Modules_FileLoad"]], $arg);
} else {
$arg = [$this->propertyClass["Class_Modules_FileLoad"]];
}
// Создание целевого модуля
$this->propertyClass["Test"] = new \Modules\TestDI(...$arg);
return $this->propertyClass["Test"];
}
}
Зависимости создаются снизу вверх: сначала классы без зависимостей, затем классы, которые их используют, и в конце — целевой модуль. Массив $arg позволяет переопределить аргументы конструктора при вызове.
Вот здесь очень хорошо видно как это работает.
Примеры использования
Пример 1. Модуль с несколькими зависимостями
<?php
// dev/modules/Logger.php
namespace Modules;
class Logger
{
public $fileLoad;
public function __construct(FileLoad $fileLoad)
{
$this->fileLoad = $fileLoad;
}
public function write($message)
{
$this->fileLoad->save('log.txt', $message);
return "Logged: $message";
}
}
<?
// dev/modules/UserService.php
namespace Modules;
class UserService
{
public $logger;
public $fileLoad;
public function __construct(Logger $logger, FileLoad $fileLoad)
{
$this->logger = $logger;
$this->fileLoad = $fileLoad;
}
public function register($name)
{
$this->logger->write("User registered: $name");
return "User $name registered";
}
}
Конфигурация в Facade:
public function realise30($arg = [])
{
return [
'User' => ['class' => 'Modules\UserService', 'arg' => []],
];
}
Контейнер рекурсивно разрешит цепочку: UserService → Logger → FileLoad → Foton\Core. Сгенерированный фасад:
public function realise30($arg = [])
{
$this->propertyClass["Class_Foton_Core"] = new \Foton\Core();
$this->propertyClass["Class_Modules_FileLoad"] = new \Modules\FileLoad(
$this->propertyClass["Class_Foton_Core"]
);
$this->propertyClass["Class_Modules_Logger"] = new \Modules\Logger(
$this->propertyClass["Class_Modules_FileLoad"]
);
if (count($arg) > 0) {
$arg = array_replace([
$this->propertyClass["Class_Modules_Logger"],
$this->propertyClass["Class_Modules_FileLoad"]
], $arg);
} else {
$arg = [
$this->propertyClass["Class_Modules_Logger"],
$this->propertyClass["Class_Modules_FileLoad"]
];
}
$this->propertyClass["User"] = new \Modules\UserService(...$arg);
return $this->propertyClass["User"];
}
Вызов:
$result = $this->core->obj('User')->realise30()->register('Иван');
Пример 2. Переопределение аргументов конструктора
Модуль со скалярным аргументом:
<?php
// dev/modules/Cache.php
namespace Modules;
class Cache
{
public $fileLoad;
public $ttl;
public function __construct(FileLoad $fileLoad, $ttl = 3600)
{
$this->fileLoad = $fileLoad;
$this->ttl = $ttl;
}
public function get($key)
{
return "Cache[$key] ttl={$this->ttl}";
}
}
Конфигурация с переопределением ttl:
public function realise40($arg = [])
{
return [
'Cache' => ['class' => 'Modules\Cache', 'arg' => ['ttl' => 60]],
];
}
public function realise41($arg = [])
{
return [
'Cache' => ['class' => 'Modules\Cache', 'arg' => ['ttl' => 86400]],
];
}
После генерации можно использовать разные конфигурации:
/ Короткий TTL
$short = $this->core->obj('Cache')->realise40()->get('user:1');
// Вывод: Cache[user:1] ttl=60
// Длинный TTL
$long = $this->core->obj('Cache')->realise41()->get('user:1');
// Вывод: Cache[user:1] ttl=86400
Краткая инструкция:
Положить модуль в dev/modules/
Описать релиз в Dev\Facade
Сгенерировать
php foton di autoload
- Использовать
$this->core->obj('ИмяФасада')->имяРелиза()->методМодуля(аргументы);




Комментарии
0