Как избежать ручной передачи контейнера $registry в конструктор каждого нового класса, который я создаю?

Я работаю с MVC уже несколько месяцев и храню все в своем объекте $registry. Когда я создаю новый класс, я обычно передаю только реестр, но мне приходится постоянно передавать $this->registry при создании нового класса.

например

class something
{
   public function __construct($registry)
   {
      $this->registry = registry;
      $this->db = $registry->db;
      $this->user = $registry->user; // ......
   }

   public function something()
   {
      $class = new something_class($this->registry);
      $class->do();
   }
}

class something_class
{
   public function __construct($registry)
   {
      $this->registry = $registry;
   }

   public function do()
   {
     echo 'Doing something ....';
   }
}

Мой вопрос в том, как я могу каким-то образом обработать передачу реестра в новый класс за кулисами (в данном случае при создании экземпляра something_class) внутри класса реестра? Я абсолютно убежден, что есть простой способ сделать это, но я нигде не могу найти ничего, связанного с тем, что я ищу.

Вот мой класс реестра:

<?php
class registry
{
    protected $vars = array();
    public function &__set($index, $value)
    {
        $this->vars[$index] = $value;
        return $value;
    }

    public function &__get($index)
    {
        return $this->vars[$index];
    }
}

person Chris98    schedule 31.08.2016    source источник
comment
Используете ли вы какой-либо фреймворк или это ваш собственный фреймворк MVC? Просто проверяю, чтобы я или кто-то еще не написал длинный ответ, который совершенно не имеет значения;)   -  person Jite    schedule 31.08.2016
comment
Спасибо за быстрый ответ, это моя собственная структура, которая очень слабо основана на этом: phpro.org/tutorials/Model-View-Controller-MVC.html Я уже добавил множество улучшений, таких как использование spl_autoload_register ** Edit ** Похоже, сайт сейчас не работает, поэтому я нашел его в пути назад, когда: https://web.archive.org/web/20160331021349/http://www.phpro.org/tutorials/Model-View-Controller-MVC.html   -  person Chris98    schedule 31.08.2016
comment
Я бы порекомендовал взглянуть на некоторые другие проекты, реализующие Dependency injection container, например: проект Symfony (на котором построено множество фреймворков) имеет хороший компонент внедрения зависимостей, который можно использовать сразу или, по крайней мере, хороший ресурс, когда дело доходит до того, как это реализовано: symfony.com/doc/current/components/dependency_injection .html   -  person Jite    schedule 31.08.2016
comment
У вас есть контейнер. Вы не должны передавать контейнер в объект. Прочтите это: php-fig.org/psr/psr-11/meta/   -  person Dennis    schedule 18.07.2017


Ответы (3)


Это все неправильно. «Реестр» — это антипаттерн, и то, что вы делаете, — это не внедрение зависимостей. Вы нашли способ подделать глобальные переменные... вот и все.

Для начала посмотрите эту лекцию.

Насчет того, как правильно сделать то, что хочется, есть два пути:

  • используйте фабрику, которая создает класс, используя предоставленные вами зависимости
  • используйте контейнер внедрения зависимостей, Auryn

Чтобы узнать, как вы используете DI-контейнер, вам просто нужно обратиться к документации. Но я объясню основы фабрики, которая больше похожа на подход «сделай сам».

Фабрика — это объект, который отвечает за инициализацию другого класса. Например, у вас есть большой набор классов, для которых требуется PDO в качестве зависимости.

class Factory 
{
    private $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }


    public function create($name) {
        return new $name($this->pdo);
    }
}

Если вы используете экземпляр этого класса, это позволит вам создавать объекты, у которых PDO уже передан как зависимость в конструкторе:

$factory = new Factory(PDO($dsn, $user, $pass));
$user = $factory->create('User');
$document = $factory->create('Doc');

И в качестве дополнительного преимущества эта настройка позволит боту экземпляра класса User и экземпляра класса Doc совместно использовать один и тот же объект PDO.

person tereško    schedule 31.08.2016
comment
Очень красивое объяснение Д.И. Тереско. Один простой комментарий, поскольку ваши темы (в частности, о MVC) являются справочными для многих новых разработчиков, я думаю, что было бы лучше, если бы в вашем ответе была ссылка на composition root, чтобы концепция DI была более ясной. - person dios231; 12.09.2016

Лучше всего, чтобы ваши классы или компоненты не зависели от вашего контейнера. Пусть ваш контейнер выполняет внедрение зависимостей.

Вот пример, в котором используется контейнер из The League of Extraordinary Packages и показана зависимость класса B от A:

<?php

$container = new League\Container\Container();

$container->add('b', function () {
    $a = new A('foo');
    $b = new B($a);

    return $b;
});

var_dump($container->get('b')->getValueFromA()); // Outputs "foo"

class A
{
    private $value;

    public function __construct($value)
    {
        $this->value = $value;
    }

    public function getValue()
    {
        return $this->value;
    }
}

class B
{
    public function __construct(A $a)
    {
        $this->a = $a;
    }

    public function getValueFromA()
    {
        return $this->a->getValue();
    }
}

Каждый написанный вами класс должен быть связан только со своими прямыми зависимостями. Если возможно, для дальнейшего разделения настоятельно рекомендуется использовать интерфейсы.

См. этот вопрос Stackoverflow для получения дополнительной информации. вокруг различий между реестром и контейнером DI. Использование реестра считается антишаблоном, и его следует избегать.

person anFfWuy7qbJWaPp6DSOR    schedule 31.08.2016

Ваш класс $registry называется шаблоном Service Locator. Это полезно в некоторых контекстах, но имеет высокий потенциал злоупотребления, особенно когда вы внедряете локатор службы в свой класс.

Предполагается, что внедрение зависимостей раскрывает (показывает) все объекты, которые использует (или зависит) ваш класс. Вместо этого внедрение $registry в ваш класс скрывает зависимости, и поэтому это анти-шаблон. Кроме того, это создает для вас бремя, поскольку вы должны передавать его повсюду, вы также можете сделать его глобальным, чтобы упростить его (это также ответит на ваш вопрос). Но есть и лучшие инструменты.

Рассмотрите свой класс так:

  • Обратите внимание на использование внедрения зависимостей $db и $user в конструктор класса something. Теперь более понятно, что нужно вашему классу для работы.
  • Также обратите внимание на использование контейнера внедрения зависимостей в этом случае Auryn для создания класса для вас.
//in your bootstrap or part of your framework
$injector = new Auryn\Injector();
$class = $injector->make('something_class');

//Your own code:
class something
{
   public function __construct(Database $db, User $user)
   {
      $this->db = $db;
      $this->user = $user;
   }

   public function something(something_class $class)
   {
      $class->do();
   }
}

Вы также можете использовать Auryn, чтобы сделать то, что вы хотите сделать, чтобы вызвать класс Registry. Но вскоре вы обнаружите, что Auryn — это более совершенная и надежная версия вашего класса Registry, которая заставляет вас использовать лучшие методы внедрения зависимостей, чтобы Auryn работал должным образом.

Основной момент заключается в том, что ваше собственное приложение (все классы вашего приложения) лучше не знать о каком-либо контейнере. Потому что контейнеры делают то, что они скрывают зависимости от ваших классов. Однако остается приемлемым использование контейнеров как части фреймворка. Пусть ваш фреймворк обрабатывает контейнеры, но вы сосредотачиваетесь на своих собственных классах, не вызывая контейнеры, если только эти контейнеры не используются как часть вашего фреймворка для подключения вашего приложения.

person Dennis    schedule 17.07.2017