Rudra FrameworkRudra Framework

Введение

Rudra использует легковесный DI-контейнер, который обеспечивает автоматическое внедрение зависимостей (autowiring) без избыточной конфигурации.
Контейнер умеет:

  • создавать объекты по типу,
  • разрешать зависимости рекурсивно,
  • кэшировать экземпляры (синглтоны),
  • работать с фабриками и замыканиями.

Регистрация сервисов

✅Сервисы регистрируются через Rudra::set(). Поддерживаются три способа:

1. Готовый экземпляр (синглтон)

Rudra::set(['logger', [new Logger()]]);

✅Один и тот же объект возвращается при каждом вызове Rudra::get('logger').

2. Фабрика через замыкание

Rudra::set(['db', [fn() => new DatabaseConnection(...)]]);

✅Новый объект создаётся при каждом вызове.

3. Класс как фабрика (с автосвязыванием)

Rudra::set(['cache', [CacheService::class]]);

✅Объект создаётся через DI-контейнер: все зависимости в конструкторе автоматически внедряются.

Получение зависимостей

$logger = Rudra::get('logger');
$db = Rudra::get('db');

Если сервис не зарегистрирован, но указан как класс ✅контейнер создаст его на лету с полным autowiring’ом.

Пример: автоматическое создание незарегистрированного класса

Допустим, у вас есть сервис:

class Mailer
{
    public function __construct(Database $db, Logger $logger)
    {
        // Зависимости будут автоматически внедрены
    }


    public function send(string $to, string $message): void
    {
        // Отправка письма
    }
}

✅Вы не регистрировали Mailer в contracts и не вызывали Rudra::set() — но можете использовать его напрямую:

#[Routing(url: 'send')]
public function send(Mailer $mailer): void
{
    $mailer->send('user@example.com', 'Hello from Rudra!');
}

Контейнер автоматически:

  1. Создаст экземпляр Mailer,
  2. Найдёт (или создаст) зависимости Database и Logger,
  3. Внедрит их в конструктор.

Автоматическое внедрение (Autowiring)

Rudra поддерживает полное автосвязывание по типам в:

  • конструкторах контроллеров,
  • методах действий (actionIndex, read, и т.д.),
  • пользовательских сервисах.
#[Routing(url: 'autowire/:id')]
public function autowire(
    RudraInterface $rudra,
    Database $db,
    CacheService $cache,
    string $id = '1'
): void {
    // Все объекты — автоматически внедрены
    // $id — взят из URL-параметра
}

⚠️Поддержка string, int, bool и других скаляров в параметрах метода — только если они соответствуют URL-маршрутам (например, :id).


Два режима работы DI

Rudra автоматически выбирает способ вызова метода в зависимости от настройки PHP:

zend.exception_ignore_args = 1

1. Режим Reflection Используется ReflectionMethod — безопасно, быстро, с кэшированием.

zend.exception_ignore_args = 0

2. Fallback через исключения Rudra ловит TypeError/ArgumentCountError и подставляет зависимости на лету.


Регистрация через конфигурацию контейнера

✅Каждый контейнер в Rudra может содержать файл config.php, в котором определяются:

  • contracts — привязка интерфейсов или классов к их реализациям;
  • services — именованные сервисы, доступные через Rudra::get('name').

Пример App/Containers/Demo/config.php:

use App\Containers\Demo\Factory\StdFactory;
use App\Containers\Demo\Factory\TestFactory;
use App\Containers\Demo\Interface\TestInterface;

return [
    'contracts' => [
        // Привязка класса к фабрике или callable
        stdClass::class      => 'callable',
        TestInterface::class => TestFactory::class
    ],

    'services' => [
        // Именованный сервис как замыкание
        'callable' => function () {
            $std = new stdClass;
            $std->method = __METHOD__ . '::Created from waiting';
            return $std;
        },
    ]
];

Как это работает:

  • При запросе Rudra::get(stdClass::class) контейнер:
    1. Смотрит в contracts,
    2. Видит, что stdClass::class → 'callable',
    3. Ищет 'callable' в services,
    4. Выполняет замыкание и возвращает результат.
  • Для TestInterface::class — используется фабрика TestFactory::class, которая создаёт объект с полным автовайрингом.

Это позволяет:

  • Изолировать зависимости внутри контейнера,
  • Подменять реализации без изменения кода,
  • Гибко комбинировать фабрики, замыкания и классы.

Преимущества такого подхода

  • Нет глобального конфига — всё привязано к контейнеру.
  • Читаемость — сразу видно, какие интерфейсы и сервисы предоставляет модуль.
  • Тестирование — легко подменить TestInterface на мок в тестах.
  • Ленивая инициализация — сервисы создаются только при первом обращении.

Пример: зависимость в конструкторе контроллера

class IndexController extends WebController
{
    public function __construct(stdClass $std)
    {
        // $std автоматически создан и внедрён
        $std->name = "Injected via DI";
        dump($std);
    }


    #[Routing(url: '')]
    public function index(stdClass $std): void
    {
        // Тот же $std доступен и в методе
        render("layout", ["content" => "Hello"]);
    }
}

✅Объекты, созданные через DI, жизнеспособны на протяжении всего запроса — вы можете передавать их между методами и сервисами.

Советы

  • Регистрируйте тяжёлые сервисы (БД, кеш) как синглтоны.
  • Используйте интерфейсы, если планируете подмену реализаций:
Rudra::set([LoggerInterface::class => [FileLogger::class]]);

⚠️Не передавайте примитивы (строки, числа) в DI — они не создаются автоматически, если не пришли из URL