Как использовать Di container?

Только вчера познакомили с DI-контейнером и вроде документация ясная и примеров куча, но не доходит до меня.
Есть
class App {
    
    private $companyService;

    public function __construct() {
        
        $container = new Container;

        $container->add('CompanyCreateService', CompanyCreateService::class)
                ->addArgument(CompanySqlRepository::class);
        $container->add(CompanySqlRepository::class)
                ->addArgument(DbConnection::class)
                ->addArgument(Hydrator::class);
        $container->add(CompanySqlRepository::class);
        $container->add(DbConnection::class);
        $container->add(Hydrator::class);
        $container->add(AdminController::class);
        $container->add('AdminController', AdminController::class)->addArgument('CompanyCreateService');
        $this->companyService = $container->get('AdminController');
        $this->actionIndex();
    }

    public function actionIndex() {
        $controller = new AdminController();
    }

Здесь я зарегистрировал контейнер и хочу запустить контроллер
class AdminController {

    public $companyService;
    
    public function __construct(CompanyServiceInterface $companyService) {
        $this->companyService = $companyService;
        $this->actionCreate();         
    }

    public function actionCreate() {
        $dto = [...]
        $this->companyService->createCompany($dto);
        echo 'saved';

Из контроллера App я запускаю AdminController без параметров.
И получаю ошибку
Fatal error: Uncaught ArgumentCountError: Too few arguments to function
, что логично, так как никакой параметр я в конструктор не передаю. Но при этом данные всё равно в базу сейвятся как должны.
Вопрос1 - нужно ли всё равно передавать параметр в конструктор вызываемого контроллера или можно просто вызывать без параметров?
Вопрос2 - где вообще надо делать $container->get('что-нибудь'); ?
В самом контроллере как-то неправильно, наверное - ибо тогда всё равно зависимость. Где это делать?
  • Вопрос задан
  • 717 просмотров
Пригласить эксперта
Ответы на вопрос 2
@Wentixon
Рекомендую почитать книгу про паттерн или вот этот сайт посмотреть, в частности паттерны стратегия. Тогда думаю все станет намного понятнее, так как сейчас у вас даже вопросы неверные, а вообще без опыта в такие дебри лучше не лезть

Если вкратце то:
Вопрос1 - нужно ли всё равно передавать параметр в конструктор вызываемого контроллера или можно просто вызывать без параметров?

У нас есть контейнер, в нем при запуске приложения мы регистрируем классы (сервисы). При регистрации мы связываем абстракцию (интерфейс) и реализацию (конкретный класс), а также впринципе говорим нашему приложению КАК создать конкретный объект. Потом же мы делаем просто так и получаем готовый объект
// Получаем объект интерфейса из любой точки приложения
$container->get(MyInterface::class);


То есть чтобы такой подход работал, нам надо где то (до запуска приложения) сказать как нам этот интерфейс создать, это мы и делаем с помощью контейнера

$container->register(MyInterface::class, function () {
  return new MyConcreteClass('какие то параметры');
});


Вопрос2 - где вообще надо делать $container->get('что-нибудь'); ? В самом контроллере как-то неправильно, наверное - ибо тогда всё равно зависимость. Где это делать?

Во-первых, контроллер это обычный класс, во-вторых принцип DI заключается в том, чтобы у вас классы не зависили от конкретных реализаций и явно ЗАВИСИЛИ от АБСТРАКЦИЙ. Это самое важно что нужно понять. Отвечая на вопрос - брать напрямую из контейнера сервисы надо ЗА сервисами, то есть в сервис провайдере и выше

Ну и кстати современные фреймворки достаточно умны и создают классы необходимые конструктору самостоятельно, если они достаточно простые, более сложные сервисы все равно надо регистрировать.

Вот короче пример написал который впринципе все объясняет, если непонятно спрашивай

interface CartRepository
{
    public function add(CartItem $item);
    public function getAll();
}

class SessionCartRepository implements CartRepository
{
    ...
}

class DbCartRepository implements CartRepository
{
    ...
}

class Cart
{
    public function __construct(CartRepository $repository) 
    {
        ...
    }

    public function getTotal()
    {
        ...
    }
}

// Service provider
$container->register(CartRepository::class, function ($container) {
    if ($user = $container->get('auth')->getUser()) {
        $repository = new DbCartRepository($container->get('db'), $user->id)
    } else {
        $repository = new SessionCartRepository();
    }

    return $repository;
});

$container->register(Cart::class, function ($container) {
    return new Cart($container->get(CartRepository::class));
});
Ответ написан
dmitriylanets
@dmitriylanets
веб-разработчик
вот хорошее решение https://container.thephpleague.com/3.x/auto-wiring/
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы