如何构建PHP依赖注入容器

时间:2013-01-08 11:55:43

标签: php dependency-injection

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

以下代码是我根据我从here学到的示例制作的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对象,因为该实例已被使用。但是,在另一个类中创建多个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错误? check this link

4 个答案:

答案 0 :(得分: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 apis来获取和保存数据。编写API的好例子是将您的论坛搜索放入自己的应用程序/搜索/名称中?partial-name = bob将返回所有用户中包含单词bob的用户。你可以建立它并随着时间的推移使它变得更好并在你的主论坛中使用它

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

答案 1 :(得分:2)

我打算写这篇评论,但它长得太长了。我不是专家,所以我将从我在几年的实践中学到的东西中提出我的观点。随意使用或质疑我的答案的任何部分(或没有)。

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(),而每个类应该能够独立行动,而不是基于[可能]其他现有类。这称为tight coupling。基本上,这就是你不应该使用全局变量的原因。类中使用的任何内容都应该传递给它,而不是在全局范围内。即使你知道它在那里并且你的代码有效,它也使得这个类不能用于其他项目,并且更难以测试。我不会扩展自己(PUN?),但是我在这里发现了一个很棒的视频,所以你可以挖掘更多: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个小时才重写一些东西,但如果必须这样做,那就去做吧。

对不起,我到处改变了你的支架风格。


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

答案 2 :(得分: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

了解更多信息。

答案 3 :(得分:1)

你提出的问题有一个非常重要的问题,我在评论中提到了这个问题。每当你检查暂停前进的可能性,以便返回并完成对现有课程的重新分解时,你必须真正考虑到收益。

replied

  

这似乎有点复杂(并且非常不同)和更多的工作,而不是在这一点上实现它的价值。我只是试图快速砍掉一些东西,看看它是否能获得牵引力。

如果您想要正确使用IoC容器实现DI,请注意以下事项:

  • 在bootstrap
  • 实施IoC容器和注册表
  • 修改所有具有依赖关系的现有类以允许setter注入,而不依赖于IoC容器本身
  • 根据需要重新编写/重新构建测试

关于第二个项目符号的说明反映了我自己的个人观点和偏好,在开发时考虑到DI可能很难而你希望可以给予他们全部奖励。最大的奖励之一是完全解耦的对象,如果一切仍然依赖于IoC容器,则不会获得完整部分。

这是很多工作,特别是如果你到现在为止还没有考虑过模式。当然,您将获得明显的好处,例如在您完成后可以使用大量可重复使用且易于测试的对象。但是,与所有重新分解一样,在添加或完成新功能和特性的上下文中没有任何新功能。难度测试或紧耦合会妨碍吗?这是你必须要权衡的东西。

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

此时你将拥有一个类的集合,可以将更好的提供给控制反转,如果这是你想要在未来追求的模式。如果它适合您并且您真的希望将其纳入您的设计决策中,那么您可以轻松地重新考虑以后再删除JIT逻辑。

总而言之,我担心如果你尝试完全实现它,你唯一会得到的结果是,现在正确是一团糟。您现在可以改变编写类前进的方式,但我不会回过头来尝试全面实现它。