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

时间:2011-10-27 02:00:55

标签: php dependency-injection

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

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

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

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

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

理论上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();
}

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

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

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

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

3 个答案:

答案 0 :(得分:3)

不看具体的实施,很难给出具体的建议,所以我会提供一般的建议,我可以(实际上我不是很好)。听起来你可以使用一个“依赖注入容器”(google it ..找一个你喜欢的或者为自己的目的滚动自己)来管理类的大型互锁依赖。

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

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

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

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();
      }
   }
}

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

正如文章作者提到的那样states here,您应该拥有专门用于构建整个对象图的代码(这是所有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接口可以从应用程序到应用程序分开,甚至可以在测试中分开。这仍然为您期望的小部件提供了一定程度的抽象。

答案 1 :(得分:0)

对我来说,依赖注入只是有用的,因为我可以选择语法$ this-> method();或$ obj-> method();调用相同的方法。这可以通过使用魔术函数set,get和call来完成。

答案 2 :(得分:0)

很抱歉花了这么长时间回复,但我不得不考虑这个问题。再一次,这不是正确的,这纯粹是我的观点和推测。这是对您问题中回复的回复:

在某种程度上,您的页面确实需要知道它需要什么样的小部件(它显然是从特定的数据库查询中获取的)。但是,它实际上不应该关心它得到的小部件。也不一定是必须将页面绑定到小部件(例如作为成员,即使这与我之前的答案相矛盾)。

事实上,我认为你的新代码大多没问题。您有小工具的工厂,即使它绑定到特定页面。我要注意的唯一问题是在new PhotoGallery类中创建PhotoGallery_Widget。你不是依赖于那里的抽象,而是依赖于结构,而且在方法中创建该对象更糟糕。您可以使用控制器中的另一个工厂创建这些必需的对象。