扩展类中的PHP依赖注入
我只是想要掌握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:我有私有函数__construct(Config $config) 或者我必须做以下事情: $config = new Config(); $dbLayer = new DatabaseLayer($config); 我想扩展DatabaseLayer类并包含与我的GameUser相关的数据库交互的方法,这是我创建的另一个类,当我扩展DatabaseLayer类时,我需要注入GameUser类 我知道我的新类DatabaseLayerUser继承了DatabaseLayer类的方法和属性. Q2:由于新的DatabaseLayerUser类基于’DatabaseLayer’类,它需要有Config类,因为我使用了__construct(Config $config)这样做会自动获取吗? 或者我必须将Config和GameUser都传递给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,$e); } } }` Q3:我看到有一个父:: __ construct(); private function __construct(Config $config,GameUser $user){ parent::__construct($config); $this->user = $user; } 解决方法
我认为你真的在这里混淆了手段和目的.目标永远不是使用依赖注入本身,而是解决您遇到的编程问题.因此,让我们首先尝试并更正类和它们的职责之间的依赖关系.
在我看来,最好从应用程序域开始这个练习,从小开始,然后重构,如果你想引入抽象.至少在学习或作为思想实验时.如果更有经验并且在实施真实的东西时,可能会更聪明地看一下前面的几个步骤. 所以现在我简单地假设你正在制作一个在线游戏,其中不同的用户由GameUser对象代表,并且没有更多. > GameUser类的唯一责任应该是表示域数据和逻辑,即:它包含一些与应用程序域本身有关的属性(比如用户名和分数)和方法(比如incrementScore)(游戏用户).它不应该对实际的实现细节负责,最明显的是:它如何被持久化(写入文件/ db /无论如何).这就是为什么负责存储自身的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的职责不是数据库连接的管理.但是它需要一个数据库对象,因此它可以将SQL查询传递给它.因此,它委派了设置连接和实际执行它将创建的SQL查询的责任. 代表团敲响了钟声.依赖注入在这里发挥作用:我们将注入一个PDO数据库对象(服务).我们将它注入构造函数(即构造函数DI而不是setter DI).然后调用者有责任弄清楚如何创建PDO服务,我们的存储库真的不在乎. 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,$e); } } public function fetchGameUser($username) { ... } public function storeGameUser(GameUser $user) { ... } } 回答Q1& Q2一气呵成:函数__construct(PDO $db)只表示一个类型约束:PHP将检查$db参数值是否为PDO对象.如果你试图运行$r = new GameUserRepository(“不是PDO对象”);它会抛出一个错误. PDO类型约束与依赖注入无关. 我认为你将这与实际在运行时检查构造函数签名的DI框架的功能混淆(使用反射),看到需要PDO类型的参数,然后确实自动创建这样的对象并将其传递给创建存储库时的构造函数.例如. Symfony2 DI包可以做到这一点,但它与PHP本身无关. 现在我们可以运行这样的代码: $pdo = new PDO($connectionString,$password); $repository = new GameUserRepository($pdo); $user = $repository->fetchGameUser('Dizzy'); 但这引出了一个问题:创建所有这些对象(服务)的最佳方法是什么,我们在哪里保留它们?当然不是仅仅将上面的代码放在某处并使用全局变量.这是需要在某处定位的两个明确的职责,所以答案是我们创建了两个新类:GameContainer类和用于创建此容器的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应用程序,其基本代码如下: $factory = new GameFactory(new GameConfig(__DIR__ . '/config.php')); $game = $factory->buildGameContainer(); 然而,一个关键的问题仍然是缺失:接口的使用.如果我们想编写一个使用外部Web服务来存储和获取GameUser对象的GameUserRepository,该怎么办?如果我们想提供一个带有灯具的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.pw')); // Factory determines which implementation to use: $repository = new PdoGameUserRepository($pdo); return new GameContainer($repository); } } 所以这真的把所有部分放在一起.我们现在可以编写一个注入MockGameUserRepository的TestGameFactory,或者更好的是,用“env.test”布尔值扩展GameConfig,并让我们现有的GameFactory类决定是否构建一个PdoGameUserRepository或MockGameUserRepository. 现在,与DI实践的联系也应该是清楚的. GameContainer当然是你的DI容器. GameFactory,是DI容器工厂.这两个是通过DI框架(如Symfony2 DI捆绑包)实现所有铃声和口哨声. 您确实可以想象将工厂及其配置扩展到所有服务在XML文件中完全定义的程度,包括它们的实现类名称: <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,以便像$container-> getService(‘GameUserRepository’)一样获取服务. 至于将构造函数参数传递给PHP中的父类(与DI几乎没有关系,除非你迟早需要使用构造函数注入),你可以按照自己的建议做到这一点: class A { private $_someService; public function __construct(SomeService $service) { $this->_someService = $service; } } class B extends class A { private $_someOtherService; public function __construct(SomeService $service,SomeOtherService $otherService) { parent::__construct($service); $this->_someOtherService = $otherService; } } $b = new B(new SomeService(),new SomeOtherService()); 但你必须远离私人建筑师.他们对单身人士充满了热情. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |