kaspi/di-container

Dependency injection container with autowired


README

Kaspi/di-container — это легковесный контейнер внедрения зависимостей для PHP >= 8.0.

Установка

composer require kaspi/di-container

Особенности

  • Autowire - контейнер автоматически создаёт и внедряет зависимости.
  • Поддержка "zero configuration for dependency injection" - когда ненужно объявлять зависимость в определениях контейнера. Если класс не имеет зависимостей или зависит только от других конкретных классов, контейнеру не нужно указывать, как разрешить этот класс.
  • Поддержка Php-атрибутов для конфигурирования сервисов в контейнере.
  • Поддержка тегов (tags) для определений и сервисов в контейнере.

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

Определения классов:

// src/Services/Envelope.php
namespace App\Services;

// Класс для создания сообщения
class Envelope {
    public function subject(string $subject): static {
        // ...
        return $this;
    }
    
    public function message(string $message): static {
        // ...
        return $this;
    }
}
// src/Services/Mail.php
namespace App\Services;

// Сервис отправки почты
class Mail {
    public function __construct(private Envelope $envelope) {}
    
    public function envelop(): Envelope {
        return $this->envelope;
    }
    
    public function send(): bool {
        // отправка сообщения 
    }
}
// src/Models/Post.php
namespace App\Models;

// Модель данных — пост в блоге.
class Post {
    public string $title;
    // ...
}
// src/Controllers/PostController.php
namespace App\Controllers;

use App\Services\Mail;
use App\Models\Post;

// Контроллер для обработки действия.
class  PostController {
    public function __construct(private Mail $mail) {}
    
    public function send(Post $post): bool {
        $this->mail->envelop()
            ->subject('Publication success')
            ->message('Post <'.$post->title.'> was published.');
        return $this->mail->send();
    }
}
use App\Controllers\PostController;
use App\Models\Post;
use Kaspi\DiContainer\DiContainerFactory;

// Создать контейнер.
$container = (new DiContainerFactory())->make();

// more code...

//Заполняем модель данными.
$post = new Post();
$post->title = 'Publication about DiContainer';

// получить класс PostController с внедренным сервисом Mail и выполнить метод "send"
$postController = $container->get(PostController::class);
$postController->send($post);

Note

Контейнер "пытается" самостоятельно определить запрашиваемую зависимость - является ли это классом или callable типом.

DiContainer выполнит следующие действия для App\Controllers\PostController:

$post = new App\Controllers\PostController(
    new App\Services\Mail(
        new App\Services\Envelope()
    )
);

Tip

Реализация кода в примере

Другой вариант для примера выше можно использовать для получения результата DiContainer::call():

use App\Controllers\PostController;
use App\Models\Post;

$post = new Post();
$post->title = 'Publication about DiContainer';

// ...

// получить класс PostController с внедренным сервисом Mail и выполнить метод "send"
$container->call(
    definition: [PostController::class, 'send'],
    arguments: ['post' => $post]
);

Tip

Больше информации о DiContainer::call()

Note

Примеры использования пакета kaspi/di-container в репозитории

Конфигурирование DiContainer

Для конфигурирования контейнера используется класс Kaspi\DiContainer\DiContainerConfig::class который реализует интерфейс Kaspi\DiContainer\Interfaces\DiContainerConfigInterface

use Kaspi\DiContainer\{DiContainerConfig, DiContainer};

$diConfig = new DiContainerConfig(
    // Ненужно объявлять каждую зависимость.
    // Если класс, функция или интерфейс существуют
    // и может быть запрошен через автозагрузку (например через composer),
    // то объявлять каждое определение необязательно.
    useZeroConfigurationDefinition: true,
    // Использовать Php-атрибуты для конфигурирования зависимостей контейнера.
    useAttribute: true,
    // Возвращать всегда одни и тот же объект (singleton pattern).
    isSingletonServiceDefault: false,
);
// передать настройки в контейнер
$container = new DiContainer(config: $diConfig);

DiContainerFactory.

Можно использовать фабрику для создания контейнера с настроенными по умолчанию параметрами:

use Kaspi\DiContainer\DiContainerFactory;

$container = (new DiContainerFactory())->make();

Конструктор фабрики:

DiContainerFactory::__construct(
    ?Kaspi\DiContainer\Interfaces\DiContainerConfigInterface $config = null
)

Tip

Можно передать другую конфигурацию контейнера в фабрику.

Зарегистрировать сконфигурированные определения (сервисы) в контейнере:

DiContainerFactory::make(
    iterable $definitions = []
): \Kaspi\DiContainer\Interfaces\DiContainerInterface

Note

Некоторые интерфейсы или классы всегда возвращают текущий контейнер зависимостей. При разрешении зависимости для интерфейсов и классов:

  • Psr\Container\ContainerInterface::class
  • Kaspi\DiContainer\Interfaces\DiContainerInterface::class
  • Kaspi\DiContainer\DiContainer::class

будет получен текущий class Kaspi\DiContainer\DiContainer::class

use Kaspi\DiContainer\DiContainerFactory;

function testFunc(\Psr\Container\ContainerInterface $c) {
    return $c;
}

$container = (new DiContainerFactory())->make();

var_dump($container->call('testFunc') instanceof DiContainer); // true
use Kaspi\DiContainer\DiContainerFactory;
use Psr\Container\ContainerInterface;

class TestClass {
    public function __construct(
        public ContainerInterface $container
    ) {}
}

$container = (new DiContainerFactory())->make();

var_dump($container->get(TestClass::class)->container instanceof DiContainer); // true

📁 DefinitionsLoader

Собирает определения для контейнера зависимостей из разных конфигурационных файлов (dependency definitions), и выполняет "импорт" классов из директорий.

Подробное описание использования DefinitionsLoader.

🧰 Подробное описание конфигурирования и использования

Тесты

Прогнать тесты без подсчёта покрытия кода

composer test

Запуск тестов с проверкой покрытия кода тестами

./vendor/bin/phpunit

Статический анализ кода

Для статического анализа используем пакет PHPStan.

composer stat
./vendor/bin/phpstan

Code style

Для приведения кода к стандартам используем php-cs-fixer который объявлен в dev зависимости composer-а

composer fixer

Использование Docker образа с PHP 8.0, 8.1, 8.2, 8.3, 8.4

Указать образ с версией PHP можно в файле .env в ключе PHP_IMAGE. По умолчанию контейнер собирается с образом php:8.0-cli-alpine.

Собрать контейнер

docker-compose build

Установить зависимости php composer-а:

docker-compose run --rm php composer install

🔔 Если установлен make в системе:

make install

Тесты

Запуск тестов без отчёта о покрытии кода:

docker-compose run --rm php vendor/bin/phpunit --no-coverage

🔔 Если установлен make в системе:

make test

Прогнать тесты с отчётом о покрытии кода:

docker-compose run --rm php vendor/bin/phpunit

🔔 Если установлен make в системе:

make test-cover

⛑ pезультаты будут в папке .coverage-html

Статический анализ кода PHPStan

docker-compose run --rm php vendor/bin/phpstan

если установлен make в системе:

make stat

Запуск комплексной проверки

Если установлен make – запуск проверки code-style, stat analyzer, tests:

make all

Другое

Можно работать в shell оболочке в docker контейнере:

docker-compose run --rm php sh