Kaspi/di-container — это легковесный контейнер внедрения зависимостей для PHP >= 8.0 с автоматическим связыванием зависимостей.
composer require kaspi/di-container
Новая сигнатура интерфейса DiContainerFactoryInterface
для метод make
:
// Для версий 1.0.x
$container = DiContainerFactory::make($definitions);
// Для версий 1.1.х и выше
$container = (new DiContainerFactory())->make($definitions);
- Примеры использования пакета kaspi/di-container в репозитории 🦄
- Примеры использования DiContainer со стандартным конфигурированием.
- Примеры использования DiContainer c PHP атрибутами.
- Конфигурация DiContainer с использованием нотаций по массиву.
Через определения зависимостей вручную в DiContainer.
Получение существующего класса и разрешение встроенных типов параметров в конструкторе:
// Определения для DiContainer
use Kaspi\DiContainer\DiContainerFactory;
$container = (new DiContainerFactory())->make(
[
\PDO::class => [
// ⚠ Ключ "arguments" является зарезервированным значением
// и служит для передачи значений в конструктор класса.
// Таким объявлением в конструкторе класса \PDO
// аргумент с именем $dsn получит значение
'arguments' => [
'dsn' => 'sqlite:/opt/databases/mydb.sq3',
],
];
]
);
// Объявление класса
namespace App;
class MyClass {
public function __construct(public \PDO $pdo) {}
}
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\MyClass;
/** @var MyClass $myClass */
$myClass = $container->get(MyClass::class);
$myClass->pdo->query('...')
Разрешение встроенных (простых) типов аргументов в объявлении:
// Объявление класса
namespace App;
class MyUsers {
public function __construct(public array $users) {}
}
class MyEmployers {
public function __construct(public array $employers) {}
}
// Определения для DiContainer
use App\{MyUsers, MyEmployers};
use Kaspi\DiContainer\DiContainerFactory;
// В объявлении arguments->users = "data"
// будет искать в контейнере ключ "data".
$definitions = [
'data' => ['user1', 'user2'],
App\MyUsers::class => [
'arguments' => [
'users' => 'data',
],
],
App\MyEmployers::class => [
'arguments' => [
'employers' => 'data',
],
],
];
$container = (new DiContainerFactory())->make($definitions);
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\{MyUsers, MyEmployers};
/** @var MyUsers::class $users */
$users = $container->get(MyUsers::class);
print implode(',', $users->users); // user1, user2
/** @var MyEmployers::class $employers */
$employers = $container->get(MyEmployers::class);
print implode(',', $employers->employers); // user1, user2
Разрешение встроенных (простых) типов аргументов в объявлении со ссылкой на другой id контейнера:
// Определения для DiContainer
use Kaspi\DiContainer\DiContainerFactory;
// В конструкторе DiContainer - параметр "linkContainerSymbol"
// определяет значение-ссылку для авто связывания аргументов -
// по умолчанию символ "@"
$container = (new DiContainerFactory())->make(
[
// основной id в контейнере
'sqlite-home' => 'sqlite:/opt/databases/mydb.sq3',
//.....
// Id в контейнере содержащий ссылку на id контейнера = "sqlite-home"
'sqlite-test' => '@sqlite-home',
\PDO::class => [
'arguments' => [
'dsn' => 'sqlite-test',
],
];
]
);
// Объявление класса
namespace App;
class MyClass {
public function __construct(public \PDO $pdo) {}
}
// ....
/** @var MyClass $myClass */
$myClass = $container->get(MyClass::class);
// в конструктор MyClass будет вызван с определением
// new MyClass(
// pdo: new \PDO(dsn: 'sqlite:/opt/databases/mydb.sq3')
// );
Разрешение типов аргументов в конструкторе по имени аргумента:
// Объявление класса
namespace App;
class MyUsers {
public function __construct(public array $listOfUsers) {}
}
// Определения для DiContainer
use Kaspi\DiContainer\DiContainerFactory;
// При разрешении аргументов конструктора можно в качестве id контейнера
// использовать имя аргумента в конструкторе
$container = (new DiContainerFactory())->make(
[
'listOfUsers' => [
'John',
'Arnold',
];
]
);
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\MyUsers;
/** @var MyUsers::class $users */
$users = $container->get(MyUsers::class);
print implode(',', $users->users); // John, Arnold
Получение класса по интерфейсу
// Объявление класса
namespace App;
use Psr\Log\LoggerInterface;
class MyLogger {
public function __construct(protected LoggerInterface $logger) {}
public function logger(): LoggerInterface {
return $this->logger;
}
}
// Определения для DiContainer
use Kaspi\DiContainer\DiContainerFactory;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Monolog\{Logger, Handler\StreamHandler, Level};
$container = (new DiContainerFactory())->make([
'logger.file' => '/path/to/your.log',
'logger.name' => 'app-logger',
LoggerInterface::class =>, static function (ContainerInterface $c) {
return (new Logger($c->get('logger.name')))
->pushHandler(new StreamHandler($c->get('logger.file')));
}
])
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\MyLogger;
/** @var MyClass $myClass */
$myClass = $container->get(MyLogger::class);
$myClass->logger()->debug('...');
Ещё один пример получение класса по интерфейсу:
// Объявление классов
namespace App;
interface ClassInterface {}
class ClassFirst implements ClassInterface {
public function __construct(public string $file) {}
}
// Определения для DiContainer
use App\ClassFirst;
use App\ClassInterface;
use Kaspi\DiContainer\DiContainerFactory;
$container = (new DiContainerFactory())->make();
// ⚠ параметр "arguments" метода "set" установить аргументы для конструктора.
$container->set(ClassFirst::class, arguments: ['file' => '/var/log/app.log']);
$container->set(ClassInterface::class, ClassFirst::class);
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\ClassInterface;
/** @var ClassFirst $myClass */
$myClass = $container->get(ClassInterface::class);
print $myClass->file; // /var/log/app.log
Конфигурирование DiContainer c PHP атрибутами для определений.
Получение существующего класса и разрешение простых типов параметров в конструкторе:
// Объявление класса
namespace App;
use Kaspi\DiContainer\Attributes\Inject;
class MyClass {
public function __construct(
#[Inject(arguments: ['dsn' => 'pdo_dsn'])]
public \PDO $pdo
) {}
}
// Определения для DiContainer
use Kaspi\DiContainer\DiContainerFactory;
$container = (new DiContainerFactory())->make(
['pdo_dsn' => 'sqlite:/opt/databases/mydb.sq3']
);
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\MyClass;
/** @var MyClass $myClass */
$myClass = $container->get(MyClass::class);
$myClass->pdo->query('...')
Использование Inject атрибута на простых (встроенных) типах для
получения данных из контейнера, где ключ "users_data" определен в контейнере:
// Объявление класса
namespace App;
use Kaspi\DiContainer\Attributes\Inject;
class MyUsers {
public function __construct(
#[Inject('users_data')]
public array $users
) {}
}
class MyEmployers {
public function __construct(
#[Inject('users_data')]
public array $employers
) {}
}
// Определения для DiContainer
use Kaspi\DiContainer\DiContainerFactory;
$definitions = [
'users_data' => ['user1', 'user2'],
];
$container = (new DiContainerFactory())->make($definitions);
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\{MyUsers, MyEmployers};
/** @var MyUsers::class $users */
$users = $container->get(MyUsers::class);
print implode(',', $users->users); // user1, user2
/** @var MyEmployers::class $employers */
$employers = $container->get(MyEmployers::class);
print implode(',', $employers->employers); // user1, user2
Получение по интерфейсу:
// Объявление классов
namespace App;
use Kaspi\DiContainer\Attributes\Inject;
use Kaspi\DiContainer\Attributes\Service;
#[Service(CustomLogger::class)]
interface CustomLoggerInterface {
public function loggerFile(): string;
}
class CustomLogger implements CustomLoggerInterface {
public function __construct(
#[Inject('logger_file')]
protected string $file,
) {}
public function loggerFile(): string {
return $this->file;
}
}
// ...
class MyLogger {
public function __construct(
#[Inject]
public CustomLoggerInterface $customLogger
) {}
}
// Определения для DiContainer
use Kaspi\DiContainer\DiContainerFactory;
$container = (new DiContainerFactory())->make([
'logger_file' => '/var/log/app.log'
]);
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\MyLogger;
/** @var MyLogger $myClass */
$myClass = $container->get(MyLogger::class);
print $myClass->customLogger->loggerFile(); // /var/log/app.log
Доступ к "контейнер-id" с вложенными определениям.
По-умолчанию символ разделитель .
Произвольный символ разделитель можно определить
Kaspi\DiContainer\DiContainer::__construct
аргумент$delimiterAccessArrayNotationSymbol
Kaspi\DiContainer\DiContainerFactory::make
аргумент$delimiterAccessArrayNotationSymbol
// Определения для DiContainer
$definitions = [
'app' => [
'admin' => [
'email' =>'admin@mail.com',
],
'logger' => App\Logger::class,
'logger_file' => '/var/app.log',
],
App\Logger::class => [
'arguments' => [
'file' => 'app.logger_file'
],
],
App\SendEmail::class => [
'arguments' => [
'from' => 'app.admin.email',
'logger' => 'app.logger',
],
],
];
$container = DiContainerFactory::make($definitions);
// Объявление классов
namespace App;
interface LoggerInterface {}
class Logger implements LoggerInterface {
public function __construct(
public string $file
) {}
}
class SendEmail {
public function __construct(
public string $from,
public LoggerInterface $logger,
) {}
}
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\SendEmail;
/** @var SendEmail $myClass */
$sendEmail = $container->get(SendEmail::class);
print $sendEmail->from; // admin@mail.com
print $sendEmail->logger->file; // /var/app.log
// Определения для DiContainer
$definitions = [
'app' => [
'admin' => [
'email' =>'admin@mail.com',
],
'logger' => App\Logger::class,
'logger_file' => '/var/app.log',
],
];
$container = DiContainerFactory::make($definitions);
// Объявление классов
namespace App;
use Kaspi\DiContainer\Attributes\Inject;
interface LoggerInterface {}
class Logger implements LoggerInterface {
public function __construct(
#[Inject('app.logger_file')]
public string $file
) {}
}
class SendEmail {
public function __construct(
#[Inject('app.admin.email')]
public string $from,
#[Inject('app.logger')]
public LoggerInterface $logger,
) {}
}
// Получение данных из контейнера с автоматическим связыванием зависимостей
use App\SendEmail;
/** @var SendEmail $myClass */
$sendEmail = $container->get(SendEmail::class);
print $sendEmail->from; // admin@mail.com
print $sendEmail->logger->file; // /var/app.log
Прогнать тесты без подсчета покрытия кода
composer test
Запуск тестов с проверкой покрытия кода тестами
./vendor/bin/phpunit
Для статического анализа используем пакет Phan.
Запуск без PHP расширения PHP AST
./vendor/bin/phan --allow-polyfill-parser
Для приведения кода к стандартам используем php-cs-fixer который объявлен в dev зависимости composer-а
composer fixer
Указать образ с версией PHP можно в файле .env
в ключе PHP_IMAGE
.
По умолчанию контейнер собирается с образом php:8.0-cli-alpine
.
Собрать контейнер
docker-compose build
Установить зависимости php composer-а:
docker-compose run --rm php composer install
Прогнать тесты с отчетом о покрытии кода
docker-compose run --rm php vendor/bin/phpunit
⛑ pезультаты будут в папке .coverage-html
Статический анализ кода Phan (static analyzer for PHP)
docker-compose run --rm php vendor/bin/phan
Можно работать в shell оболочке в docker контейнере:
docker-compose run --rm php sh