13

我最近了解到在我的 PHP 应用程序中使用依赖注入 (DI) 的优势。但是,我仍然不确定如何为依赖项创建容器,或者我是否应该在我正在构建的在线论坛中使用 DI。

以下代码是我根据从这里学到的示例制作的 DI 容器版本。

class ioc {

   var $db;
   var $session;
   var $user_id;

   static function newUser(static::$db, static::$user_id) {
      $user = new User($db, $user_id);
      return $user;
   }

   static function newLogin(static::$db, static::$session) {
      $login = new Login($db, $session);
      return $login;
   }

}

$user = ioc::newUser();
$login = ioc::newLogin();

我有几个问题:

1) 我应该在哪里实例化我注入的依赖项,例如 $database、$session 等?它是在容器类之外,还是在容器的构造函数内。

2)如果我需要在其他类中创建用户类的多个实例怎么办?我无法注入先前实例化的 $user 对象,因为该实例已被使用。但是,在另一个类中创建多个 User 实例会违反 DI 的规则。例如:

class Users {

    function __construct($db, $user_id) {
        $this->db = $db;
        $this->user_id = $user_id;
    }

    function create_friends_list() {
        $st = $this->$db->prepare("SELECT user_id FROM friends WHERE user_id = $this->user_id");
        $st->execute();
        while($row = $st->fetch()) {
            $friend = ioc::newUser($row['user_id']);
            $friend->get_user_name();
            $friend->get_profile_picture();
        }
    }
}   

3) 我想知道我是否应该采用 DI,因为我知道我必须重写我以前的所有代码。我以前一直依赖于我在我的 initialize.php 中实例化的全局变量,它包含在我的所有文件中。

在我看来,DI 会产生很多开销,并且在某些情况下它无法使用(如我的 #2 示例中)。以下网站来自一位开发人员,他列举了许多不使用 DI 的充分理由。他的论点有任何依据吗?还是我只是用错了DI? 检查这个链接

4

4 回答 4

7

我应该在哪里实例化我注入的依赖项,例如 $database、$session 等?它是在容器类之外,还是在容器的构造函数内。

理想情况下,您的数据库连接和会话将被引导。正确的 DI 需要一个基础对象的实例,所有内容都注册到该实例中。因此,以您的 IOC 类为例,您需要创建它的实例($ioc = new IOC();)然后您需要某种服务提供者类说

$ioc->register('database', new DatabaseServiceProvider($host, $user, $pass))

现在,每次您想要连接到数据库时,您只需要传递$ioc->get('database');一个非常粗略的示例,但我认为您可以看到这个想法基本上是将所有内容存储在注册表中,并且没有任何东西是静态绑定的,这意味着您可以创建另一个$ioc完全不同的设置可以很容易地创建连接来表示不同的数据库以进行测试。

如果我需要在其他类中创建 User 类的多个实例怎么办?我无法注入先前实例化的 $user 对象,因为该实例已被使用。但是,在另一个类中创建多个 User 实例会违反 DI 的规则。

这是一个常见问题,有多种不同的解决方案。首先,您的 DI 应该显示登录用户和仅用户之间的区别。您可能想要注册您的登录用户,但不仅仅是任何用户。使您的用户类正常并使用

$ioc->register('login-user', User::fetch($ioc->get('database'), $user_id));

所以现在$ioc->get('login-user')返回您的登录用户。然后,您可以使用User->fetchAll($ioc->get('database'));来获取所有用户。

我想知道我是否应该采用 DI,因为我知道我必须重写我以前的所有代码。我以前一直依赖于我在我的 initialize.php 中实例化的全局变量,它包含在我的所有文件中。

如果您需要重写所有代码以使用 DI,您可能不应该这样做。如果有时间,也许可以考虑制作一个新项目并使用一些旧代码。如果您的代码库很大,我建议您将其分解为较小的项目并使用 RESTFUL api 来获取和保存数据。编写 API 的好例子是将您的论坛搜索放入它自己的应用程序 /search/name?partial-name=bob 将返回所有包含单词 bob 的用户。您可以构建它并随着时间的推移使其变得更好,并在您的主论坛中使用它

我希望您能理解我的回答,但如果您需要更多信息,请告诉我。

于 2013-01-08T13:29:33.770 回答
2

我打算写这篇评论,但它太长了。我不是专家,所以我将根据我通过几年的实践和在 SO 中学到的知识来发表我的观点。随意使用或质疑我答案的任何部分(或没有)。

1.- 外面。容器有什么作用?答案应该是单一的。它不应该负责初始化类、连接到数据库、处理会话和其他事情。每个班级只做一件事。

class ioc
  {
  public $db;

  // Only pass here the things that the class REALLY needs
  static public function set($var, $val)
    {
    return $this->$var = $val;
    }

  static function newDB($user, $pass)
    {
    return new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    }

  static function newUser($user_id)
    {
    return new User($db, $user_id);
    }

  static function newLogin($session)
    {
    return new Login($this->db, $session);
    }
  }

if (ioc::set('db',ioc::newDB($user, $pass)))
  {
  $user = ioc::newUser($user_id);
  $login = ioc::newLogin($session);
  }

2.- 你不应该$friend = ioc::newUser($row['user_id']);在课堂上做。在那里你假设有一个ioc用一个方法调用的类newUser(),而每个类都应该能够自己行动,而不是基于[可能]其他现有的类。这称为紧耦合。基本上,这就是为什么你也不应该使用全局变量的原因。在一个类中使用的任何东西都应该传递给它,而不是假设在全局范围内。即使您知道它在那里并且您的代码可以工作,它也会使该类无法在其他项目中重用并且更难测试。我不会扩展自己(双关语?),但会在 SO 中放一个我在这里发现的很棒的视频,这样你就可以挖掘更多:The Clean Code Talks - Don't Look For Things

我不确定该类的User行为方式,但这就是我的做法(不是必须的):

// Allow me to change the name to Friends to avoid confusions
class Friends
  {
  function __construct($db)
    {
    $this->db = $db;
    }

  function create_friends_list($user_id)
    {
    if (!empty(id))
      {
      // Protect it from injection if your $user_id MIGHT come from a $_POST or whatever
      $st = $this->$db->prepare("SELECT user_id FROM friends WHERE user_id = ?");
      $st->execute(array($user_id));
      $AllData = $st->fetchAll()
      return $AllData;
      }
    else return null;
    }

  // Pass the $friend object
  function get_friend_data($friend)
    {
    $FriendData = array ('Name' => $friend->get_user_name(), 'Picture' => $friend->get_profile_picture());
    return $FriendData;
    }
  }

$User = ioc::newUser($user_id);
$Friends = new Friends($db);

$AllFriendsIDs = array();
if ($AllFriendsIDs = $Friends->create_friends_list($User->get('user_id')))
  foreach ($AllFriendsIDs as $Friend)
    {
    // OPTION 1. Return the name, id and whatever in an array for the user object passed.
    $FriendData = $Friends->get_friend_data(ioc::newUser($Friend['user_id']));
    // Do anything you want with $FriendData

    // OPTION 2. Ditch the get_friend_data and work with it here directly. You're already in a loop.
    // Create the object (User) Friend.
    $Friend = ioc::newUser($Friend['user_id']);
    $Friend->get_user_name();
    $Friend->get_profile_picture();
    }

我没有测试它,所以它可能有一些小错误。

3.- 如果你在编码的同时学习,你将不得不重写很多东西。尝试从一开始就做一些事情,这样您就不需要重写所有内容,而只需要重写类/方法并为所有代码采用一些约定。例如,永远不要从函数/方法内部回显,总是从外部返回和回显。我会说是的,这是值得的。仅仅重写一些东西就必须浪费 1 或 2 个小时,这很糟糕,但如果必须这样做,那就去做吧。

PS,对不起,我到处都改变了你的括号样式。


编辑阅读其他答案,虽然您不应该使用您的 ioc 对象连接到数据库,但使用它创建一个新对象应该是完美的。在上面编辑以了解我的意思。

于 2013-01-08T13:56:26.423 回答
1

你问的问题有一个非常重要的问题,我在评论中提出了这个问题。每当您检查暂停前进进度以返回并完成对现有类的不那么微不足道的重构的可能性时,您都必须真正考虑收益。

回复

这似乎有点复杂(而且非常不同)并且比此时值得实施的工作更多。我只是想快速破解一些真正的东西,看看它是否会获得牵引力。

请记住,如果您想用 IoC 容器正确实现 DI,则需要执行以下操作:

  • 在引导程序中实现 IoC 容器和注册表
  • 修改所有具有依赖关系的现有类以允许 setter 注入,而不依赖于 IoC 容器本身
  • 根据需要重写/重组测试

关于第二个项目符号的注释反映了我自己的个人观点和偏好,在考虑 DI 的情况下进行开发可能会很困难,你想要它可以给予的全部回报。最大的回报之一是完全解耦的对象,如果一切仍然依赖于 IoC 容器,您将无法获得完整的部分。

这是很多工作,特别是如果您直到现在还没有考虑这种模式。当然,您将获得明显的好处,例如完成后有许多可重用且易于测试的对象。但是,与所有重构一样,在添加或完成新功能和特性的上下文中不会完成任何新的事情。是困难测试还是紧耦合阻碍了这一点?这是你必须权衡的事情。

我建议您在编写新代码时记住该模式。提供 setter 方法来注入依赖项,可以手动使用,也可以通过 IoC 容器使用。但是,如果没有注入任何类,请让您的类继续及时创建它们,并避免在构造函数中组合。

那时,您将拥有一组更好地支持控制反转的类,如果这是您将来想要追求的模式。如果它对您很有效,并且您确实希望将其纳入您的设计决策中,那么您可以轻松地重构以在以后删除 JIT 逻辑。

总之,我担心如果你现在尝试完全实施它,你最终会得到的唯一结果就是一团糟。您现在可以更改编写课程的方式,但我不会回去尝试全面实施它。

于 2013-01-09T01:46:36.497 回答
1

而不是 init.php 中的全局变量定义您的对象,例如:

ioc::register('user', function() {
 return new User();
});

并使用您的 create_friends_list 方法:

ioc::get('user')->newUser($user_id);

这是一个非常简单的实现。查看: http ://net.tutsplus.com/tutorials/php/dependency-injection-huh/?search_index=2

或者

http://net.tutsplus.com/tutorials/php/dependency-injection-in-php/?search_index=1

了解更多信息。

于 2013-01-08T13:10:47.510 回答