Diz*_*igh 3 php oop dependency-injection class extend
我只是想要掌握OOP,很抱歉,如果这个问题似乎有点遍布整个地方,那就是我现在的感觉.
我已经在PHP文档中查看了构造函数,但它似乎没有涵盖依赖注入.
我有一个名为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;
}
}
Run Code Online (Sandbox Code Playgroud)
Q1:我有private function __construct(Config $config)
我不知道我完全理解其原因__construct(Config $config),而不是仅仅使用__construct($config)
不(Config $config)自动创建$config一个新的Config实例?
或者我必须做以下事情:
$config = new Config();
$dbLayer = new DatabaseLayer($config);
Run Code Online (Sandbox Code Playgroud)
我想扩展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, 0, $e);
}
}
}`
Run Code Online (Sandbox Code Playgroud)
Q3:我看到有一个parent::__construct();
意思是我应该使用:
private function __construct(Config $config, GameUser $user){
parent::__construct($config);
$this->user = $user;
}
Run Code Online (Sandbox Code Playgroud)
wka*_*ann 19
我认为你真的在这里混淆了手段和目的.目标永远不是使用依赖注入本身,而是解决您遇到的编程问题.因此,让我们首先尝试并更正类和它们的职责之间的依赖关系.
在我看来,最好从应用程序域开始这个练习,从小开始,然后重构,如果你想引入抽象.至少在学习或作为思想实验时.如果更有经验并且在实施真实的东西时,可能会更聪明地看一下前面的几个步骤.
所以现在我只是假设你正在制作一个在线游戏,其中不同的用户由GameUser对象代表,并且没有更多.
GameUser该类的唯一责任应该是表示域数据和逻辑,即:它包含与应用程序域本身(游戏用户)有关的几个属性(比如说username和score)和方法(比方说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;
}
}
Run Code Online (Sandbox Code Playgroud)
我们现在可以创建一个new GameUser($user = new GameUser('Dizzy', 100);),但是我们不能坚持它.所以我们需要某种存储库,我们可以存储这些用户并在以后再次获取它们.我们称之为:GameUserRepository.这是一个服务类.稍后当存在多种类型的域对象和存储库时,我们可以创建一个DatabaseLayer将这些组合和/或作为外观的类,但是我们现在从小开始并稍后重构.
同样,GameUserRepository该类的职责是允许我们获取和存储GameUser对象,并检查是否存在给定用户名的配置文件.原则上,存储库可以将GameUser对象存储在文件或其他地方,但是现在,我们在这里做出选择以将它们保存在SQL数据库中(从小处开始,稍后重构,你得到它.)
的责任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, 0, $e);
}
}
public function fetchGameUser($username) { ... }
public function storeGameUser(GameUser $user) { ... }
}
Run Code Online (Sandbox Code Playgroud)
要一次性回答Q1和Q2:function __construct(PDO $db)只需表达类型约束:PHP将检查$db参数值是否为PDO对象.如果您正在尝试运行$r = new GameUserRepository("not a PDO object");,则会抛出错误.该PDO类型约束无关,与依赖注入.
我想你与DI框架的功能,实际上在运行时(使用反射)检查构造函数签名的混乱这一点,看到需要一个PDO型参数,然后自动的的确创建这样一个对象,并把它传递给创建存储库时的构造函数.例如,Symfony2 DI包可以做到这一点,但它与PHP本身无关.
现在我们可以运行这样的代码:
$pdo = new PDO($connectionString, $user, $password);
$repository = new GameUserRepository($pdo);
$user = $repository->fetchGameUser('Dizzy');
Run Code Online (Sandbox Code Playgroud)
但这引出了一个问题:创建所有这些对象(服务)的最佳方法是什么,我们在哪里保留它们?当然不是仅仅将上面的代码放在某处并使用全局变量.这些是需要在某处定位的两个明确的职责,所以答案是我们创建了两个新类:创建此容器的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);
}
}
Run Code Online (Sandbox Code Playgroud)我们现在可以想象一个game.php基本上有以下代码的应用程序:
$factory = new GameFactory(new GameConfig(__DIR__ . '/config.php'));
$game = $factory->buildGameContainer();
Run Code Online (Sandbox Code Playgroud)
然而,一个关键的问题仍然是缺失:接口的使用.如果我们想编写一个GameUserRepository使用外部Web服务来存储和获取 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);
}
}
Run Code Online (Sandbox Code Playgroud)
所以这真的把所有部分放在一起.我们现在可以编写一个TestGameFactory注入MockGameUserRepository,或者更好的是,GameConfig使用"env.test"布尔值进行扩展,并让我们现有的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>
Run Code Online (Sandbox Code Playgroud)
你也可以设想一般化,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());
Run Code Online (Sandbox Code Playgroud)
但你必须远离私人建筑师.他们对单身人士充满了热情.