php - 开发业务代码时应该使用 IoC 依赖注入(inject)吗?

标签 php design-patterns dependency-injection ioc-container

现代 PHP 框架(如 Zend、Symfony、Phalcon)都使用 DI 容器,您只需传递它即可访问所有框架功能。我想知道我是否应该/可以在我的业务代码中使用 DI 容器。假设我需要使用一个数据库访问对象和一个邮件程序对象,并且它们已经在 DI 中,因为框架使用它们。我可以在实例化业务类时简单地传递 DI 容器吗?

例如,我有一个处理数据库中用户的类,你可以称它为我的用户模型类。现在,当在 Controller 中实例化它时,我只是将 DI 容器传递给模型类的构造函数,这很简单。只需将所有东西扔进 DI 容器即可完成。

但我即将开发一个也将使用此用户模型类的 API。由于它需要一个 DI 容器,我需要事先知道模型的依赖项是什么,并使用正确的依赖项初始化 DI 容器。以前,我只是将每个依赖项作为参数传递到构造函数中,但是对于 IoC,我需要知道,无需查看参数,类的依赖项是什么以及用于访问每个依赖项的名称是什么。例如,我需要知道 DI 容器中应该有一个由“db”标识的 PDO 对象。对于业务/图书馆代码来说,这是一种好的方法吗?

我可能在这里混淆了术语,但我希望你明白这一点。

谢谢!

最佳答案

您开发的代码类型并不重要,无论是业务逻辑还是框架逻辑(或其他类型的逻辑),关键在于您如何处理类依赖性。

术语业务逻辑本身就非常抽象。您可以用单个类(也称为 domain object)表示业务逻辑,也可以将业务逻辑表示为 layer。 .

需要注意的基本事项:数据库只是一个存储引擎

当你开发任何应用程序时,你应该记住数据库是可以改变的(或者你可以在未来迁移到 NoSQL 解决方案)。当/如果你这样做了,那么 $pdo 就不再是一个依赖项。如果您的存储逻辑与业务逻辑完全分离,那么替换存储引擎就足够容易了。否则你最终会在更改时重写很多东西。

适当设计的架构鼓励将存储逻辑与应用程序逻辑分离。这些模式被确立为最佳实践:Data Mapper 或 Table Gateway

namespace Storage\MySQL;

use PDO;

abstract class AbstractMapper
{
     protected $pdo;

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

class UserMapper extends AbstractMapper
{
    private $table = 'cms_users';

    public function fetchById($id)
    {
       $query = sprintf('SELECT * FROM `%s` WHERE `id` =:id', $this->table);
       $stmt = $this->pdo->prepare($query);
       $stmt->execute(array(
           ':id' => $id
       ));

       return $stmt->fetch();
    }

    // the rest methods that abstract table queries
}

所以在这种情况下,对于当前的存储引擎,$pdo 是核心依赖项,它不是框架或您正在开发的应用程序的依赖项。您应该在这里解决的下一个问题是如何自动化将 $pdo 依赖项传递给映射器的过程。您只能利用一种解决方案 - 工厂模式

$pdo = new PDO();
$mapperFactory = new App\Storage\MySQL\Factory($pdo);

$mapperFactory->build('UserMapper'); // will return UserMapper instance with injected $pdo dependency

现在让我们看看明显的好处:

首先,它的可读性——任何看到代码的人都会得到线索,Mapper 被用来抽象表访问。其次,您可以轻松更换存储引擎(如果您计划将来迁移并添加多个数据库支持)

$mongo = new Mongo();
$mapperFactory = new App\Storage\Mongo\Factory($mongo);

$mapperFactory->build('UserMapper'); // will return UserMapper instance with injected $mongo dependency

Note: All mappers for different storage engines should implement one interface (API enforcement)

模型不应该是一个类

当谈到网络时,我们基本上会做以下事情:

  • 呈现表单(可能看起来像联系页面、登录表单等)
  • 然后提交表单
  • 然后与我们的验证规则进行比较
  • 成功时我们将表单数据插入存储引擎,失败时我们显示错误消息

因此,当您将模型实现为一个类时,您最终会在同一个类中编写验证和存储逻辑,因此紧耦合并破坏了 SRP。

这个常见的例子之一,你可以在 Yii Framework 中看到

适当的模型应该是包含应用程序逻辑的类的文件夹(参见 ZF2 或 SF2)。

最后,您真的应该使用 DiC 吗?

开发代码时应该使用 DI 容器吗?好吧,让我们看看这个经典代码示例:

class House
{
      public function __construct($diContainer)
      {
             //Let's assume that door and window have their own dependencies
             // So no, it's not a Service Locator in this case
             $this->door = $diContainer->getDoor();
             $this->window = $diContainer->getWindow();
             $this->floor = $diContainer->getFloor();
      }
}

$house = new House($di)

在这种情况下,您告诉您House 取决于DiC 而不是明确地取决于门、窗和地板。但是等等,我们的 House 真的依赖 DiC 吗?当然不是。

当你开始测试你的类(class)时,你还必须提供准备好的 DiC(这完全不相关)

通常人们总是使用 Di 容器来避免注入(inject)。但从另一方面来说,由于大多数 DI 容器都是基于配置的,因此需要一些 RAM 和一些时间来解析。

好的架构可以免于任何 Di 容器,因为它利用了工厂和 SRP。

那么好的应用程序组件应该没有任何服务定位器和 Di 容器。

关于php - 开发业务代码时应该使用 IoC 依赖注入(inject)吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26222400/

相关文章:

php - Kohana - 你把 AJAX 脚本放在哪里?

javascript - 使用 Ember.js,处理键盘事件的常见模式有哪些?

android - Dagger 无法注入(inject)类型参数字段

c# - Autofac 多次注册组件

php - 有没有办法防止表透视/映射中出现重复条目​​?

PHP - 检查数组中有多少个值

design-patterns - 装饰器模式;在两个抽象类下装饰具体组件

java - guice:自动绑定(bind)泛型类

php - Google Fit API 无法添加大部分范围

php - 使用 Where 子句在数据库列中查找最大值