在PHP Web应用程序中使用依赖项注入

Jon*_*han 10 php dependency-injection

我最近读了很多关于依赖注入的内容,理论上它已经很有意义了.容易测试,单一责任类的想法听起来很聪明.但是,我正在努力解决这个问题.

我正在开发一个PHP Web应用程序,它现在有一个CMS组件,并且是由数据库驱动的.在CMS中,您可以创建页面,页面本身使用各种小部件构建.小部件可以是直接HTML(来自WYSIWYG编辑器),也可以更复杂,如照片库.这是重要的部分,照片库小部件依赖于其他数据库模型,如PhotoGalleryPhotoGalleryImages为了正常运行.

根据MiškoHevery,我不应该通过我的应用程序的不同层传递对依赖项的引用.相反,每个类应该简单地"询问"它所需的任何依赖性.使用他的例子,这是看起来如何:

$db = new Database()
$repo = new UserRepository($db);
$page = new LoginPage($repo);
Run Code Online (Sandbox Code Playgroud)

虽然我可以看到这对我的应用程序的服务层工作正常,但我真的不明白这对我的值对象/实体如何工作.在我看来,这种模式要求我在我的应用程序的最高层实例化我的所有模型.但是,当我需要我的页面类告诉我需要实例化哪些小部件时,我如何在该级别实例化我的所有小部件?如果不知道我正在创建什么小部件,我怎么知道需要创建和注入哪些小部件依赖?

从理论上讲,DI对我来说很有意义.但是,实际上在我的新Web应用程序中实现此模式更加困难.对于DI和IoC,还有一些我缺少的东西吗?谢谢!

更新:对Tandu的回应

非常感谢Tandu的回应.我认为DI在这里可能实际上不是我的问题,因为我认为我非常清楚它是什么,无论是构造函数还是setter注入,还是使用容器或穷人的方式完成.我认为正在发生的事情是DI正在挑战我重新思考我目前的编程方式.我认为我的OOP设计是个问题.为了说明这一点,以下是我通常编程此问题的方法:

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();
}
Run Code Online (Sandbox Code Playgroud)

显然这里有一些问题.该Page模型取决于Widget模型.该PhotoGallery_Widget模型依赖于PhotoGallery模型,并进一步对PhotoGallery模型依赖于PhotoGalleryImages模型.问题是,开发这种方式对我来说效果很好.随着我对应用程序层的深入和深入,每个层都负责创建所需的对象.引入DI会给我的思维方式带来重大影响.但是,我想学习如何正确地做到这一点.仅仅因为这对我现在有用,并不意味着我应该继续这样做.

你提到使用工厂.但是,工厂是否真的打破了DI,因为它们正在实例化对象?

您可能还注意到我的模型有责任与数据库进行交互.我怀疑这也是我设计的问题.我应该使用某种数据映射来从我的模型中删除此责任吗?

鉴于我目前正在实施我的代码的这个更具体的例子,您是否有任何其他建议,或者您是否可以进一步说明我应该采用不同的方式?

Exp*_*lls 3

如果不考虑具体的实现,就很难给出具体的建议,所以我将提供我能提供的一般建议(实际上我不是很好)。听起来您可以使用“依赖项注入容器”(谷歌搜索..找到一个您喜欢的容器或根据您的目的自行推出)来管理类的大型互锁依赖项。

您应该记住的一件事是您的代码应该轻轻地落在设计周围,这将非常牢固地支撑它。您不必努力让代码遵循您创建的设计。如果您尝试做的事情不符合您对设计的理解,那么可能是时候重写了。

您也只使用构造函数注入的示例。我相信您知道在适当的时候可以使用其他类型的注入,例如 setter 注入。

正如我所说,我还没有看到你的代码,但你的设计的问题在于,页面不应该知道它需要什么小部件,而是服务。如果您将特定的小部件实现绑定到页面,则会违反依赖倒置(并使依赖注入变得更加困难)。相反,您的代码应该更符合以下内容:

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();
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

该服务将创建一个页面并添加它所需的小部件。页面不应该关心这些小部件。您可能还必须在页面和小部件之间实现桥梁。该服务也可以处理这个问题。

正如您在这里提到的文章作者所说,您应该拥有专门用于构建整个对象图的代码(这是所有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);
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

接口WidgetFactory可以与应用程序分离,甚至在测试中也可以分离。这仍然为您提供了 Page 期望的小部件的一定程度的抽象。