我使用的是 PHP 5.6.x:这是作弊,而不是依赖注入,但同时它可能会对您有所帮助。至少您可以将 PDO 包装器注入其他对象(用户等)。此外,此代码假定您希望将存储过程与准备好的语句一起使用。
abstract class Storage //Program to an interface. Not an implementation.
{
}
abstract class Database extends Storage //Program to an interface. Not an implementation.
{
protected $pdo = null;
protected $stmt = null;
public function __construct(PDO $pdo)
{
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo = $pdo;
}
public function __destruct()
{
$this->pdo = null;
$this->stmt = null;
unset($this->pdo, $this->stmt);
}
protected function getDSN()
{
return "{$this->rdms}:host={$this->host};port={$this->port};dbname={$this->dbName};charset={$this->charset};"
}
/* Place all wrapping methods here, etc. */
private function validatePDOStmtObject(PDOStatement $stmt)
{
$this->stmt = $stmt;
return true;
}
public function query($sp)
{
return $this->validatePDOStmtObject($this->pdo->query("CALL {$sp}"));
}
public function prepare($sp)
{
try
{
$this->validatePDOStmtObject($this->pdo->prepare("CALL {$sp}"));
}
catch (\PDOException $pdoEx)
{
throw new \RuntimeException("Failed to prepare query / stored procedure.<br>" . print_r($this->pdo->errorInfo(), true) . $pdoEx->getTraceAsString());
}
return;
}
public function bindParam($parameter, $variable)
{
$dataType = null;
if(is_string($variable))
{
$dataType = PDO::PARAM_STR;
}
elseif(is_int($variable))
{
$dataType = PDO::PARAM_INT;
}
elseif(is_bool($variable))
{
$dataType = PDO::PARAM_BOOL;
}
elseif($variable === null)
{
$dataType = PDO::PARAM_NULL;
}
else
{
$dataType = PDO::PARAM_LOB;
}
//Where the actual binding takes place.
if(!$this->stmt->bindParam($parameter, $variable, $dataType))
{
throw new \RuntimeException("Failed to bind paramenter $parameter" . print_r($this->stmt->errorInfo(), true));
}
return;
}
public function execute()
{
$flag = false;
try
{
$this->stmt->execute();
$flag = true;
}
catch (\PDOException $pdoEx)
{
error_log($pdoEx->getTraceAsString() . '<br><br>' . print_r($this->stmt->errorInfo(), true));
//echo $pdoEx->getTraceAsString() . '<br><br>' . print_r($this->stmt->errorInfo(), true);
}
return $flag;
}
public function fetch()
{
return $this->stmt->fetch();
}
public function fetchColumn()
{
return $this->stmt->fetchColumn();
}
public function fetchAll()
{
$rows = $this->stmt->fetchAll();
$this->clearRowsets();
$this->stmtClose();
return $rows;
}
public function clearRowsets()
{
if(isset($this->stmt))
{
while($this->stmt->fetch())
{
if(!$this->stmt->nextRowset())
{
break;
}
}
}
return;
}
public function stmtClose()
{
$this->stmt = null;
return;
}
public function closeCursor()
{
$this->stmt->closeCursor();
return;
}
public function close()
{
$this->pdo = null;
return;
}
public function startTransaction()
{
if($this->pdo->beginTransaction())
{
//'<br>Starting PDO/MySQL transaction.<br>';
error_log('<br>Starting PDO/MySQL transaction.<br>');
}
else
{
throw new \RuntimeException('Failed to prepare the PDO statement.<br>' . print_r($this->pdo->errorInfo(), true));
}
return;
}
public function commit()
{
if($this->pdo->commit())
{
//echo '<br>Committing datbase changes.<br>';
error_log('<br>Committing datbase changes.<br>');
}
else
{
throw new \RuntimeException('PDO was unable to commit the last statement.<br>' . print_r($this->pdo->errorInfo(), true));
}
return;
}
public function rollback()
{
if($this->pdo->rollback())
{
//echo '<br>Rolling back datbase changes.<br>';
error_log('<br>Rolling back datbase changes.<br>');
}
else
{
throw new \RuntimeException('PDO was unable to rollback the last statement.<br>' . print_r($this->pdo->errorInfo(), true));
}
return;
}
}
最后,您可以设计一个具体的子类。
class MySQL extends Database //Now, build you concrete implementation.
{
protected $rdms = 'mysql';
protected $host = null;
protected $port = null;
protected $dbName = null;
protected $charset = null;
private static $instance = null;
// PURE DI would start here by injecting a PDO object.
// However, the PDO object must be configured, too.
// It is possible to do PURE PDO DI.
public function __construct($host = null, $port = null, $dbName = null, $charset = null)
{
require_once 'MySQLCreds.php';
//$host, $port, $dbName, and $charset can be stored in an
//include, or be supplied as arguments to the MySQL constructor.
$this->host = $host;
$this->port = $port;
$this->dbName = $dbName;
$this->charset = $charset;
parent::__construct(new PDO($this->getDSN(), $username, $password, $options));
// Starting here with DI is cheating, but it gets you on the
// right track for now. Database::getDSN() is used to dynamically
// construct the DSN.
}
/* Destructor */
public function __destruct()
{
$this->rdms = null;
$this->host = null;
$this->port = null;
$this->dbName = null;
$this->charset = null;
unset($this->rdms, $this->host, $this->port, $this->dbName, $this->charset);
parent::__destruct();
}
/* Only if you wanted to make a singleton. In that case,
make the constuctor private.
*/
public static function getInstance()
{
if(!isset(self::$instance))
{
self::$instance = new self();
}
return self::$instance;
}
}
您可能将其实例化为:
$user = new User(new MySQL())
//Uses an include inside of the constructor.
这也是可能的。
$user = new User(new MySQL($host, $port, $dbName, $charset))
//Externally supplied arguments.
有些人使用依赖注入器容器、工厂或单例。选择你的毒药。无论如何,尝试实现一个自动加载器并使用命名空间。注意:请注意此序列可能导致的PDOException。酌情使用try/catch。
哦,顺便说一下,User 的构造函数可能看起来像这样。
abstract class Person
{
protected $db = null;
public function __construct(Database $db)
{
$this->db = $db
}
}
class User extends Person
{
public function __construct(Database $db)
{
parent::__construct($db)
}
}
从技术上讲,我可以使用 Storage 的类型提示,但这需要使用公共接口方法填充 Storage 类,这些方法调用统一的具体实现方法(在 Storage 的子类中定义:数据库、XMLFile、文件等)。在某些情况下,这不是一个可能的多态解决方案。但是,您可以换出任何类型的数据库,它应该仍然可以工作。