Успешно ли разыменование объекта PHP 5.4 уменьшает недостатки статического параметра хранилища в этом контейнере DI? - PullRequest
8 голосов
/ 07 февраля 2012

ОБНОВЛЕНИЕ ОБЩЕСТВЕННОГО ОБСЛУЖИВАНИЯ:

Я многому научился с тех пор, как изначально задал этот вопрос.Если вы читаете это, пожалуйста, примите мой совет и вообще избегайте static.Просто.Не.Используйте.Это. Нет способа внедрения зависимостей;путь внедрения зависимостей.


Недавно я потратил много времени на изучение различных концепций инверсии управления ( IOC ).Я полностью согласен с теми, кто считает Service Locator антипаттерном.Я построил один, чтобы повозиться с ним, и был ошеломлен мощью, которую он позволял импортировать «глобальные» сущности в середине классов, используя статические методы локатора, а также возможность скрывать фактические зависимости объекта.

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

Вот простой примертакой реализации:

<?php

class Container
{
  protected static $params = [];

  public function store($key, $val)
  {
    static::$params[$key] = $val;
    return $this;
  }

  public function fetch($key)
  {
    if (isset(static::$params[$key])) {
      return static::$params[$key];
    }
    $msg = "No parameter match found in container: $key";
    throw new OutOfBoundsException($msg);
  }
}

$container = new Container;
$container->store('widgetDep', new WidgetDependency);
$container->store('kumquatDep', new KumquatDependency);

// and somewhere else in the application without access to the global namespace
// (i.e. the $container instance we just created) ...

$widget  = new Widget(new Container);
$kumquat = new Kumquat(new Container);

Это кажется шагом в правильном направлении, потому что статическое свойство $params защищено, и нет статических методов для доступа или манипулирования им в «глобальной» статической области:объект требует доступа к контейнеру для доступа к зависимостям.

Ой, подождите ...

К сожалению, хранение зависимостей в этом контейнере означает, что теперь каждый объект с внедрением зависимостей имеет поддельноезависимость от объекта контейнера, таким образом скрывая его real зависимости. Еще один негативный побочный эффект состоит в том, что каждому объекту будет предоставлен доступ ко всем доступным зависимостям в контейнере, и, очевидно, объект Widget не должен иметь доступа к * 1035.* Kumquat Зависимости объекта.Кроме того, использование абстрактной фабрики с таким подходом делает только перемещение фиктивной зависимости из классов Widget и Kumquat в фабрику.

Альтернатива PHP 5.4

Благодаря новым возможностям разыменования конструкции объекта 5.4 мы могли бы сделать что-то вроде следующего без необходимости доступа к уже созданному экземпляру $container, который существует в глобальном пространстве имен:

$widget  = new Widget((new Container)->fetch('widgetDep'));
$kumquat = new Kumquat((new Container)->fetch('kumquatDep'));

Используя этот подход, мы успешно:

  1. Устранили зависимость контейнера от объектов Widget и Kumquat, позволяя их конструкторам указывать конкретные объекты зависимости, которые им требуются;
  2. Предотвращение нижестоящего Widgetи объекты Kumquat, имеющие доступ к зависимостям, о которых они не должны знать, существуют;
  3. Сохранены возможности хранения статических зависимостей.

Теперь возможный недостаток заключается в том, что этот подход означает, что разработчик должен быть дисциплинированнымдостаточно, чтобы не передать полный Container объект в качестве зависимостиndency.Это очень важно.

Итак, вопрос ...

В двух частях:

  1. Какие конкретные недостатки вы видите при таком подходе, и
  2. Статический Container::$params вообще необходим?Должно ли оно вместо этого быть стандартным защищенным свойством, доступ к которому производят классы / методы фабрики графов top-of-the-object-графа в глобальном пространстве имен в любом случае (устраняя необходимость в static)?

Ответы [ 3 ]

5 голосов
/ 07 февраля 2012

Вы не должны использовать static здесь вообще.Просто создайте контейнер: $container = new DIContainer(); и используйте этот объект как типичную зависимость.В конце концов, в ядре приложения очень мало мест, которым требуется доступ ко всему контейнеру.

Взгляните на Компонент Sypenfion Dependency Injection - кусок неплохого кода.


РЕДАКТИРОВАТЬ:

Согласно первому комментарию.Да, вы меня неправильно поняли.Обычно вам требуется всего несколько зависимостей от контейнера, поэтому вы напишите что-то вроде:

$service = new Service($container->get('dep.a'), $container->get('dep.b'), 123);

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

global $container;
$widget  = new Widget($container->fetch('widgetDep'));
$kumquat = new Kumquat($container->fetch('kumquatDep'));

$widget  = new Widget(Container::getInstance()->fetch('widgetDep'));
$kumquat = new Kumquat(Container::getInstance()->fetch('kumquatDep'));

// You're using new objects but they share the same, **global** array.
// Therefore, they are actually global themselves.
$widget  = new Widget((new Container())->fetch('widgetDep'));
$kumquat = new Kumquat((new Container())->fetch('kumquatDep'));

Другими словами, сам Контейнер должен быть локальной переменной, и если вам потребуется доступ к нему где-то еще (некоторым объектам может потребоваться доступ ко всему контейнеру).) тогда вы должны явно передать его как зависимость от этого объекта.

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


Простой контейнер:

class Container {
    private $services = array();

    public function get($service) {
        if (!array_key_exists($this->services, $service)) {
            throw ...;
        }

        return $this->services[$service];
    }
}

$containerA = new Container();
$containerB = new Container();

// $containerA and $containerB are completely different 
// objects and don't share anything
2 голосов
/ 01 сентября 2012

Мне не нравится идея создания нового контейнера и совместного использования глобального массива.

Решение создания объекта фасада мне кажется лучше:

class IoC
{
  private static $container;

  public static function Initialize ( IContainer $Container )
  {
    self::$container = $Container;
  }

  public static function Resolve( $type, array $parameters = array() )
  {
    return self::$container->Resolve( $type, $parameters );
  }
}

В начальной загрузке IoC может быть инициализирован:

$container = new Container();
$container->Register( 'Logger', function() { return new Logger('somefile.log'); } );
IoC::Initialize ( $container );

И использоватьконтейнер:

$log = IoC::Resolve( 'Logger' );

Имхо лучшее решение, чем симфония "решение".Контейнер может быть легко заменен другой реализацией без изменения какого-либо другого кода.А для тестирования просто используйте новый экземпляр «контейнера» без объекта фасада.

0 голосов
/ 07 февраля 2012

Я использую класс как этот от Laravel . Это позволяет мне делать такие вещи:

// Create a PDO connection instance
IoC::register('pdo', function($config)
{
    return new PDO($config['dsn'], $config['user'], $config['pass'], $config['params']);
});

// Create a Database instance using the default PDO connection instance
IoC::register('database', function($config)
{
    return new Database(IoC::resolve('pdo', $config, $config['identifier'], $config['type']));
});

$db = IoC::resolve('database', array('user' => 'root', 'pass' => '', ...));

Это означает, что я могу зарегистрировать каждый класс (или группу классов) как небольшой подключаемый модуль, который обрабатывается контейнером IoC. Это позволяет мне переопределять эти объекты по мере необходимости для таких вещей, как модульные тесты, поскольку каждый класс никогда не создается специальным образом в остальной части кода PHP.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...