Использование внедрения зависимостей в веб-приложении PHP - PullRequest
10 голосов
/ 27 октября 2011

В последнее время я много читал о внедрении зависимостей, и в теории это имело большой смысл.Идея иметь простые для тестирования классы с одной ответственностью просто звучит умно.Тем не менее, я борюсь с тем, как далеко это сделать.

Я сейчас работаю над веб-приложением PHP, которое имеет компонент CMS и в значительной степени управляется базой данных.В CMS вы можете создавать страницы, а сами страницы создаются с использованием различных виджетов.Виджеты могут быть простым HTML (из WYSIWYG-редактора) или могут быть немного более сложными, например, фотогалерея.И это важная часть, виджет фотогалереи зависит от других моделей БД, таких как PhotoGallery и PhotoGalleryImages, для правильного функционирования.

Согласно Miško Hevery , яне следует передавать ссылки на зависимости через разные уровни моего приложения.Вместо этого каждый класс должен просто «запрашивать» любые зависимости, которые ему требуются.Используя его пример, вот как это выглядит:

$db = new Database()
$repo = new UserRepository($db);
$page = new LoginPage($repo);

Хотя я вижу, что это работает нормально для сервисных уровней моего приложения, я действительно не понимаю, как это будет работать с моими объектами / сущностями значений,Мне кажется, что этот шаблон требует от меня создания экземпляров всех моих моделей на самом высоком уровне моего приложения.Тем не менее, как я могу создать все свои виджеты на этом уровне, когда мне нужен класс моей страницы, чтобы сказать мне, какие виджеты мне нужно создать?И, не зная, какие виджеты я создаю, как я могу узнать, в каких зависимостях виджетов нужно создавать и внедрять?

Теоретически DI имеет для меня большой смысл.Однако на самом деле реализовать этот шаблон в моем новом веб-приложении оказывается сложнее.Есть ли еще что-то, что мне не хватает в отношении DI и IoC?Спасибо!

Обновление: ответ Танду

Большое спасибо за ответ Танду.Я думаю, что DI на самом деле не может быть моей проблемой здесь, потому что я думаю, что у меня есть четкое понимание того, что это, будь то инъекция конструктора или сеттера, или это делается с помощью контейнера или способом бедняка.Я думаю, что происходит, что DI заставляет меня переосмыслить то, как я сейчас программирую.Я думаю, что мой дизайн ООП является проблемой.Чтобы проиллюстрировать это, вот как я обычно программировал бы эту проблему:

class Page
{
    public $id;
    public $name;

    public function widgets()
    {
        // Perform SQL query to select all widgets
        // for this page from the database, and then
        // return them as Widget objects.
    }
}

interface Widget
{
    public $id;
    public $page_id;
    public $type;

    public function widgetize();
}

class PhotoGallery_Widget implements Widget
{
    public $id;
    public $page_id;
    public $type;

    public function widgetize()
    {
        $gallery = new Photogallery();

        foreach($gallery->images())
        {
            // Create gallery HTML code
        }
    }
}

// Finally, to create the page, I would:
$page = new Page(1);
foreach($page->widgets() as $widget)
{
    $widget->widgetize();
}

Очевидно, здесь есть некоторые проблемы.Модель Page зависит от моделей Widget.Модель PhotoGallery_Widget зависит от модели PhotoGallery, и еще больше модель PhotoGallery зависит от модели PhotoGalleryImages.Дело в том, что развитие этого пути хорошо для меня.По мере того, как я все глубже и глубже проникаю в слои своего приложения, каждый слой берет на себя ответственность за создание необходимых ему объектов.Представление DI бросает главный рывок в мое мышление.Однако я хочу научиться делать это правильно.То, что это работает для меня прямо сейчас, не означает, что я должен продолжать делать это таким образом.

Вы упоминаете об использовании фабрик.Но разве фабрики не нарушают DI, потому что они являются объектами-экземплярами?

Вы также можете заметить, что мои модели несут ответственность за взаимодействие с базой данных.Я подозреваю, что это также проблема с моим дизайном.Должен ли я использовать какое-либо отображение данных, чтобы снять эту ответственность с моих моделей?

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

Ответы [ 3 ]

3 голосов
/ 27 октября 2011

Без рассмотрения конкретных реализаций сложно дать конкретный совет, поэтому вместо этого я дам общий совет, который могу (на самом деле я не очень хорош). Похоже, вы могли бы использовать «контейнер для инъекций зависимостей» (Google google. Найдите тот, который вам нравится, или бросьте свой собственный для своих целей), который управляет большими взаимосвязанными зависимостями классов.

Одна вещь, которую вы должны иметь в виду, это то, что ваш код должен мягко подходить к дизайну, который будет очень сильно его поддерживать. Вам не нужно усердно работать, чтобы заставить ваш код следовать дизайну, который вы создали. Если то, что вы пытаетесь сделать, не работает в соответствии с вашим пониманием дизайна, возможно, пришло время переписать.

Вы также используете только примеры внедрения конструктора. Я уверен, что вы знаете, что при необходимости можно использовать другие виды инъекций, такие как инъекция сеттера.

Как я уже сказал, я не видел ваш код, но проблема с вашим дизайном в том, что не страница должна знать о том, какие виджеты ему нужны, а сервис. Если вы привязываете конкретные реализации виджетов к странице, это нарушает инверсию зависимостей (и усложняет внедрение зависимостей). Вместо этого ваш код должен быть примерно таким:

interface Widget {
   function widgetize();
}

class ConcretePage {
   private $widgets = array();

   public function addWidget(Widget $widget) {
      $this->widgets[] = $widget;
   }

   public function run() {
      foreach ($this->widgets as $widget) {
         $widget->widgetize();
      }
   }
}

Сервис создаст страницу и добавит необходимые виджеты. Страница не должна заботиться об этих виджетах. Возможно, вам также придется реализовать мост между страницей и виджетами. Служба тоже справится с этим.

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

Это может быть не в духе DI, но более простое решение будет иметь WidgetFactory:

class Page {
    private $wf;
    private $widgets = array('whiz', 'bang');
    public function __construct(WidgetFactory $wf) {
       $this->wf = $wf;
    }
    public function widgetize() {
       foreach ($this->widgets as $widget) {
          $widget = $this->wf::build($widget);
       }
    }
}

Интерфейс WidgetFactory может быть отдельным от приложения к приложению и даже в тестировании. Это все еще дает некоторый уровень абстракции для ожидаемых виджетов, которые ожидает Страница.

0 голосов
/ 28 декабря 2011

Извините, что так долго отвечал, но мне пришлось много думать об этом. Еще раз, это не правильно , это чисто мое мнение и предположение. Это ответ на ваш вопрос:

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

На самом деле, я думаю, что ваш новый код в основном в порядке. У вас есть фабрика для виджетов, даже если она привязана к определенной странице. Единственная проблема, которую я хотел бы отметить, - это создание new PhotoGallery в классе PhotoGallery_Widget. Вы зависите не от абстракции, а от конкреции, и еще хуже создать этот объект в методе. Вы можете создать эти необходимые объекты с помощью другой фабрики в контроллере.

0 голосов
/ 27 октября 2011

Для меня внедрение зависимостей полезно только потому, что я могу выбрать между синтаксисом $ this-> method ();или $ obj-> method ();вызвать тот же метод.Это можно сделать с помощью магических функций set, get и call.

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