扩展类中的PHP依赖注入

时间:2014-04-21 11:13:59

标签: php class oop dependency-injection extend

我刚刚接触OOP,很抱歉,如果这个问题看起来有点遍布,那就是我现在的感觉。

我看过constructors in the PHP docs,但似乎没有涉及依赖注入。

我有一个名为DatabaseLayer的类,这个类只是创建了一个与我的数据库的连接

//php class for connecting to database
/**
 * Class DatabaseLayer - connects to database via PDO
 */
class DatabaseLayer
{
    public $dbh; // handle of the db connexion
    /**
     * @param $config Config- configuration class
     */
    private function __construct(Config $config)
    {
        $dsn =  $config->read('db.dsn');
        $user = $config->read('db.user');
        $password = $config->read('db.password');
        $this->dbh = new \PDO($dsn, $user, $password);
    }

    public function getConnection()
    {
        if ($this->dbh)
        {
            return $this->dbh;
        }
        return false;
    }
}

Q1:我有private function __construct(Config $config) 我不确定我完全理解__construct(Config $config)而不是仅使用__construct($config)的原因 (Config $config)是否会自动将$config创建为新的Config实例?

或者我必须执行以下操作:

$config = new Config();
$dbLayer = new DatabaseLayer($config);

我想扩展DatabaseLayer类并包含与我的GameUser相关的数据库交互的方法,这是我创建的另一个类,当我扩展我需要的DatabaseLayer类时注入GameUser

我知道我的新班级DatabaseLayerUser继承了DatabaseLayer班级的方法和属性。

Q2:,因为新的DatabaseLayerUser课程基于' DatabaseLayer' class需要有Config类,因为我使用__construct(Config $config)这会自动获取吗?

或者我必须同时将ConfigGameUser传递给DatabaseLayerUser

class DatabaseLayerUser EXTENDS DatabaseLayer
{

    private $config;    /** @var Config   */
    private $user;      /** @var GameUser */

    /**
     * @param Config    $config
     * @param GameUser  $user
     */
    private function __construct(Config $config, GameUser $user){
        $this->config = $config;
        $this->user = $user;
    }
    /**
     * profileExists - Checks to see if a user profile exists
     * internal @var $PDOQuery
     * @var $PDOStatement PDOStatement
     * @throws Exception - details of PDOException
     * @return bool
     */
    private function profileExists()
    {
        try{
            $PDOQuery = ('SELECT count(1) FROM userprofile_upl WHERE uid_upl = :uid');
            $PDOStatement = $this->dbh->prepare($PDOQuery);
            $PDOStatement->bindParam(':uid', $this->_userData->uid);
            $PDOStatement->execute();
            return $PDOStatement->fetchColumn() >0;
        }catch(PDOException $e){
            throw  new Exception('Failed to check if profile exists for '.$this->_userData->uid, 0, $e);
        }
    }
}`

问题3:我看到有一个parent::__construct(); 这是否意味着我应该使用:

private function __construct(Config $config, GameUser $user){
            parent::__construct($config);
            $this->user = $user;
        }

1 个答案:

答案 0 :(得分:18)

我认为你真的在这里混淆了手段和目的。目标永远不是使用依赖注入本身,而是解决您遇到的编程问题。因此,让我们首先尝试并稍微纠正类之间的依赖关系。

在我看来,最好从应用程序域开始这个练习,从小开始,然后重构,如果你想引入抽象。至少在学习或作为思想实验时。如果经验更丰富,实施真实的东西,那么前面几步可能会更聪明。

所以现在我只是假设您正在制作一个在线游戏,其中不同的用户由GameUser对象代表,并且不会超过这个。

  • GameUser类的唯一责任应该是表示域数据和逻辑,即:它包含几个属性(让我们说username和{{ 1}})和方法(让我们说score)与应用程序域本身(游戏用户)有关。它应该负责实际的实现细节,最值得注意的是:它如何被持久化(写入文件/ db /等)。这就是为什么负责存储自身的incrementScore可能是一个坏主意。因此,让我们从一个漂亮而干净的DatabaseLayerUser域类开始,并使用封装(私有属性以防止从外部篡改):

    GameUser

    我们现在可以创建一个新的class GameUser { private $_username; private $_score; public function __construct($username, $score) { $this->_username = $username; $this->_score = $score; } public function getUsername() { return $this->_username; } public function getScore() { return $this->_score; } public function incrementScore() { $this->_score++; return $this->_score; } } GameUser),但我们无法坚持下去。所以我们需要某种存储库,我们可以存储这些用户并在以后再次获取它们。我们称之为:$user = new GameUser('Dizzy', 100);。这是一个服务类。稍后,当存在多种类型的域对象和存储库时,我们可以创建一个GameUserRepository类来对这些类进行分组和/或充当外观,但我们现在开始很小,稍后将重构。

  • 同样,DatabaseLayer类的职责是允许我们获取和存储GameUserRepository个对象,并检查是否存在给定用户名的配置文件。原则上,存储库可以将GameUser对象存储在文件或其他地方,但是现在,我们在这里做出选择以将它们保存在SQL数据库中(从小开始,稍后重构,你得到它。)

    GameUser的责任是管理数据库连接。但是它需要一个数据库对象,因此它可以将SQL查询传递给它。因此,它委托设置连接并实际执行它将创建的SQL查询的责任。

    代表团敲响了钟声。依赖注入在这里发挥作用:我们将注入一个PDO数据库对象(服务)。我们将它注入构造函数(即构造函数DI而不是setter DI)。然后,调用者有责任弄清楚如何创建PDO服务,我们的存储库真的不在乎。

    GameUserRepository

    回答Q1& Q2中的一个:class GameUserRepository { private $_db; public function __construct(PDO $db) { $this->_db = $db; } public function profileExists($username) { try { $PDOQuery = ('SELECT count(1) FROM userprofile_upl WHERE uid_upl = :uid'); $PDOStatement = $this->dbh->prepare($PDOQuery); $PDOStatement->bindParam(':uid', $username); $PDOStatement->execute(); return $PDOStatement->fetchColumn() >0; } catch(PDOException $e) { throw new Exception('Failed to check if profile exists for '. $username, 0, $e); } } public function fetchGameUser($username) { ... } public function storeGameUser(GameUser $user) { ... } } 只表示一个类型约束:PHP将检查function __construct(PDO $db)参数值是否为PDO对象。如果您尝试运行$db,则会产生错误。 $r = new GameUserRepository("not a PDO object");类型约束与依赖注入无关。

    我认为你将这与在运行时实际检查构造函数签名的DI框架的功能混淆(使用反射),看到需要PDO类型的参数,然后确实创建了这样的对象自动创建存储库时将其传递给构造函数。例如。 Symfony2 DI包可以做到这一点,但它与PHP本身无关。

    现在我们可以运行这样的代码:

    PDO

    但这引出了一个问题:创建所有这些对象(服务)的最佳方法是什么,我们在哪里保留它们?当然不是仅仅将上面的代码放在某处并使用全局变量。这些是需要在某处定位的两个明确的职责,所以答案是我们创建了两个新类:$pdo = new PDO($connectionString, $user, $password); $repository = new GameUserRepository($pdo); $user = $repository->fetchGameUser('Dizzy'); 类和GameContainer类来创建这个容器。

  • GameFactory课程的职责是将GameContainer服务与我们将来创建的其他服务集中在一起。 GameUserRepository类的职责是设置GameFactory对象。我们还会创建一个GameContainer类来配置我们的GameConfig

    GameFactory

我们现在可以想象一个class GameContainer { private $_gur; public function __construct(GameUserRepository $gur) { $this->_gur = $gur; } public function getUserRepository() { return $this->_gur; } } class GameConfig { ... } class GameFactory { private $_config; public function __construct(GameConfig $cfg) { $this->_config = $cfg; } public function buildGameContainer() { $cfg = $this->_config; $pdo = new PDO($cfg->read('db.dsn'), $cfg->read('db.user'), $cfg->read('db.pw')); $repository = new GameUserRepository($pdo); return new GameContainer($repository); } } 应用程序,其基本代码如下:

game.php

然而,仍然缺少一个关键的事情:接口的使用。如果我们要编写使用外部Web服务来存储和获取$factory = new GameFactory(new GameConfig(__DIR__ . '/config.php')); $game = $factory->buildGameContainer(); 个对象的GameUserRepository,该怎么办?如果我们想提供一个GameUser的灯具以方便测试怎么办?我们无法:我们的MockGameUserRepository构造函数明确要求GameContainer对象,因为我们已使用GameUserRepository服务实现了该对象。

所以,现在是时候从PDO重构和提取界面了。当前GameUserRepository的所有消费者现在都必须使用GameUserRepository界面。这可以归功于依赖注入:对IGameUserRepository的引用都只是用作我们可以由接口替换的类型约束。如果我们没有将创建这些服务的任务委托给GameUserRepository(现在有责任确定每个服务接口的实现),那么它就不可能

我们现在得到这样的结果:

GameFactory

所以这真的把所有部分组合在一起。我们现在可以编写 interface IGameUserRepository { public function profileExists($username); public function fetchGameUser($username); public function storeGameUser(GameUser $user); } class GameContainer { private $_gur; // Container only references the interface: public function __construct( IGameUserRepository $gur ) { $this->_gur = $gur; } public function getUserRepository() { return $this->_gur; } } class PdoGameUserRepository implements IGameUserRepository { private $_db; public function __construct(PDO $db) { $this->_db = $db; } public function profileExists($username) {...} public function fetchGameUser($username) { ... } public function storeGameUser(GameUser $user) { ... } } class MockGameUserRepository implements IGameUserRepository { public function profileExists($username) { return $username == 'Dizzy'; } public function fetchGameUser($username) { if ($this->profileExists($username)) { return new GameUser('Dizzy', 10); } else { throw new Exception("User $username does not exist."); } } public function storeGameUser(GameUser $user) { ... } } class GameFactory { public function buildGameContainer(GameConfig $cfg) { $pdo = new PDO($cfg->read('db.dsn'), $cfg->read('db.user'), $cfg->read('db.pw')); // Factory determines which implementation to use: $repository = new PdoGameUserRepository($pdo); return new GameContainer($repository); } } 注入TestGameFactory,或者更好的是,使用" env.test"扩展MockGameUserRepository。 boolean并让我们现有的GameConfig类根据它来决定是构建GameFactory还是PdoGameUserRepository

现在也应该清楚与DI实践的联系。当然,MockGameUserRepository是您的DI容器。 GameContainer是DI容器工厂。这两个是通过DI框架(如Symfony2 DI捆绑包)实现所有铃声和口哨声。

您确实可以想象将工厂及其配置扩展到所有服务在XML文件中完全定义的程度,包括它们的实现类名称:

GameFactory

您还可以设想概括<container env="production"> <service name="IGameUserRepository" implementation="PdoGameUserRepository"> <connectionString>...</connectionString> <username>...</username> <password>...</password> </service> </container> <container env="test"> <service name="IGameUserRepository" implementation="MockGameUserRepository"/> </container> ,以便像GameContainer一样提取服务。


至于将构造函数参数传递给PHP中的父类(与DI几乎没有关系,除非你迟早需要使用构造函数注入),你可以按照自己的建议做到这一点:

$container->getService('GameUserRepository')

但是你必须远离私人建筑师。他们对单身人士充满了热情。