如何构建PHP依赖注入容器

16 浏览
0 Comments

如何构建PHP依赖注入容器

我最近学习了在PHP应用程序中使用依赖注入(Dependency Injection, 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对象,因为该实例已被使用。然而,在另一个类中创建多个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?

查看此链接

admin 更改状态以发布 2023年5月23日
0
0 Comments

我本想将此作为评论撰写,但它变得太长了。我不是专家,因此我只会从我在实践中学到的东西和我的一些看法来回答。请随意使用或者质疑我的回答中的任何部分(或者都不用)。

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 上发现的很棒的视频:代码整洁之道 - 不要寻找事物

我不确定类 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. 如果你是在编码中学习的话,则你将需要重写许多东西。尝试从一开始就做对某些事情,这样你就不需要重写所有的东西,只需要重写几个类或者方法,然后采用一些适用于你所有代码的约定。例如,永远不要从函数/方法内部使用 echo,而是始终从外部返回和输出 echo。我敢说是值得这样做的。虽然不好的是你可能不得不花费一个或两个小时来重写某些东西,但如果必须这样做,那就去完成它吧。

顺便说一句,抱歉,我已经在所有地方改变了你的括号风格。


编辑
阅读其他答案时,虽然你不应该使用 ioc 对象连接数据库,但是使用它来创建一个新对象是完全可以的。上文已对此进行了修改,以便您了解我所指的含义。

0
0 Comments

我的注入依赖项 (例如 $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 将返回所有带有该单词的用户。您可以逐步构建它并使其更好,并在主论坛中使用它。

我希望你能理解我的回答,如果需要更多信息,请让我知道。

0