Пакет · di

Быстрый старт

От установки до полностью автовайренного сервиса за пять минут: вы опишете небольшой граф объектов, дадите контейнеру собрать его по типам и вызовете метод контроллера с внедрёнными зависимостями — не написав ни одного new.

Время ~5 минУровень НачальныйТребуется PHP 8.4+

Что вы соберёте

UserService, который зависит от UserRepository, а тот — от DatabaseConnection. Вы объявите эти зависимости параметрами конструктора, и контейнер соберёт всю цепочку одним вызовом make(). Затем вы вызовете метод контроллера через call(), позволив контейнеру подставить аргументы. Цикл такой: описать → bootstrap → make → call.

Перед началом

  • PHP ≥ 8.4 (проверьте командой php -v)

Установите пакет:

bash
composer require flytachi/winter-di

Шаг 1 — Опишите сервисы

Дайте каждому классу scope-атрибут и объявите зависимости типизированными параметрами конструктора. #[Singleton] означает один общий экземпляр на процесс.

src/Service.php
<?php

use Flytachi\Winter\DI\Attribute\Singleton;

#[Singleton]
class DatabaseConnection
{
  public function query(string $sql): array
  {
      // притворимся, что это обращение к настоящей базе
      return [['id' => 1, 'name' => 'Ada'], ['id' => 2, 'name' => 'Linus']];
  }
}

#[Singleton]
class UserRepository
{
  // DatabaseConnection автовайрится по своему типу
  public function __construct(private DatabaseConnection $db) {}

  public function all(): array
  {
      return $this->db->query('SELECT * FROM users');
  }
}

#[Singleton]
class UserService
{
  public function __construct(private UserRepository $repo) {}

  public function names(): array
  {
      return array_column($this->repo->all(), 'name');
  }
}

Нет атрибута? Тоже работает

Класс без scope-атрибута по умолчанию transient (новый экземпляр каждый раз) и всё равно автовайрится. Атрибут лишь делает время жизни явным. См. Scope’ы.

Шаг 2 — Инициализируйте контейнер

Создайте контейнер один раз, затем дайте Scanner найти аннотированные классы и зарегистрировать их автоматически.

bootstrap.php
<?php

require 'vendor/autoload.php';

use Flytachi\Winter\DI\Container;
use Flytachi\Winter\DI\Scanner;
use Flytachi\Winter\DI\Collector\DICollector;

$container = Container::init();

Scanner::run(__DIR__ . '/src')
  ->collect(new DICollector($container))
  ->execute();

Шаг 3 — Разрешите сервис через make()

Запросите у контейнера UserService. Он читает конструктор, видит, что нужен UserRepository, которому нужен DatabaseConnection, и строит для вас всю цепочку.

php
$service = $container->make(UserService::class);

print_r($service->names());
// Array ( [0] => Ada [1] => Linus )

Поскольку все классы #[Singleton], второй make(UserService::class) вернёт тот же самый экземпляр — граф строится один раз и кэшируется.

Шаг 4 — Вызовите метод с внедрением

Контроллеры и команды редко создают свои зависимости сами. call() вызывает метод и разрешает его параметры из контейнера — даже те, которых у самого класса нет.

src/UserController.php
<?php

class UserController
{
  public function index(UserService $service): array
  {
      return $service->names();
  }
}
php
$names = $container->call([UserController::class, 'index']);

print_r($names);
// Array ( [0] => Ada [1] => Linus )

Контейнер создал UserController, увидел, что index() нужен UserService, разрешил его (переиспользовав синглтон) и вызвал метод. Если вы видите два имени — весь граф собрался за вас. 🎉

Всё целиком

Полный вариант для копирования:

app.php
<?php

require 'vendor/autoload.php';

use Flytachi\Winter\DI\Container;
use Flytachi\Winter\DI\Attribute\Singleton;

#[Singleton]
class DatabaseConnection
{
  public function query(string $sql): array
  {
      return [['id' => 1, 'name' => 'Ada'], ['id' => 2, 'name' => 'Linus']];
  }
}

#[Singleton]
class UserRepository
{
  public function __construct(private DatabaseConnection $db) {}
  public function all(): array { return $this->db->query('SELECT * FROM users'); }
}

#[Singleton]
class UserService
{
  public function __construct(private UserRepository $repo) {}
  public function names(): array { return array_column($this->repo->all(), 'name'); }
}

class UserController
{
  public function index(UserService $service): array { return $service->names(); }
}

$container = Container::init();

print_r($container->call([UserController::class, 'index']));
// Array ( [0] => Ada [1] => Linus )

Здесь Scanner не нужен

Этот однофайловый пример пропускает шаг со Scanner: make() и call() автовайрят конкретные классы по требованию даже без регистрации. Scanner важен, когда код разбит на множество файлов и нужно автоматически применять scope-атрибуты — см. Сканирование и автообнаружение.

Что только что произошло

  • Вы ни разу не вызвали new для сервиса — контейнер прочитал типы и построил граф рекурсивно.
  • #[Singleton] задал время жизни — каждый сервис создаётся один раз и переиспользуется.
  • make() разрешил класс; call() разрешил параметры метода и вызвал его.
  • Зависимости текут по типу — объявленный класс параметра и есть проводка.

Следующие шаги