我试图找出在不使用单例方法的情况下将PDO与其他类一起使用的最佳方法。我已经在stackoverflow上搜索了几十个问题,但我仍然不清楚如何做到这一点。我明白,显然依赖注入是可行的方法,但我不太确定我是否理解它。这就是我提出来的。
class MyPDO extends PDO {
public function run($sql, $args = NULL) {
$stmt = $this->prepare($sql);
$stmt->execute($args);
return $stmt;
}
public function addNew($table, $values) {
$this->run('INSERT INTO ' . $table . ' (first_name) VALUES (?)', $values);
}
}
class User {
private $database = null;
public function __construct(Database $database) {
$this->database = $database;
}
public function register($user) {
$this->database->addNew('users', $user);
}
}
$pdo = new MyPDO("mysql:host=".DB_HOST.";dbname=".DB_NAME,DB_USER,DB_PASS);
$user = new User($pdo);
$user->register(array('name'));
我不确定这是否是一种好的方式,或者我是否会偏离基础。是否应该像MyPDO类一样在MyPDO类内部进行连接?另外,我想知道将用户插入数据库是否应该像现在一样在MyPDO类中,或者我是否应该在User类中创建一个函数来插入数据库。任何帮助表示赞赏。
答案 0 :(得分:1)
我是编写MyPDO“包装器”的那个人。在没有无关紧要的情况下回答你的问题:
我不确定这是不是一个好的方法,或者我是否离开基地。
是的,这很好。
是否应该像MyPDO类一样在MyPDO类内部进行连接?
最好在内部进行,因为除了建立连接之外,您还必须添加一些配置选项
如果将用户插入数据库应该像现在一样在MyPDO类中
绝不 - 不! 想想如果你有十几个课程会是什么样的!
所以它会是
class MyPDO extends PDO {
public function __construct($dsn, $username, $password, $options) {
$default_options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$options = array_merge($default_options, $options)
parent::__construct($dsn, $username, $password, $options);
}
public function run($sql, $args = NULL) {
$stmt = $this->prepare($sql);
$stmt->execute($args);
return $stmt;
}
}
class User {
private $database = null;
private $table = "users";
public function __construct(Database $database) {
$this->database = $database;
}
public function register($user_data) {
$this->database->run('INSERT INTO ' . $this->table . ' (first_name) VALUES (?)', $user_data);
}
}
$pdo = new MyPDO("mysql:host=".DB_HOST.";dbname=".DB_NAME,DB_USER,DB_PASS);
$user = new User($pdo);
$user->register(array('name'));
答案 1 :(得分:0)
我使用的是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
类,这些方法调用统一的,具体的实现方法(在存储的子类中定义):数据库,XML文件,文件等)。在某些情况下,这不是一个可能的多态解决方案。但是,你可以换掉任何类型的数据库,它应该可以工作。
答案 2 :(得分:-1)
昨天我遇到了同样的问题。我认为使用依赖注入也是一种方法。
我无法告诉你我的方式是否是最好的方式,但它对我来说非常好。
首先关闭你的课程MyPDO带走了很多PDO为你提供的功能(See here)。
我解决了这个问题:
class Database {
public $con;
function __construct($dsn, $user, $password) {
$this->connect($dsn, $user, $password);
}
private function connect($dsn, $user, $password) {
try {
$this->con = new PDO($dsn, $user, $password, array(
PDO::ATTR_PERSISTENT => true
));
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
}
}
class User {
private $database;
public function __construct(Database $database) {
$this->database = $database;
}
public function register($user) {
$stmt = $this->database->prepare("
INSERT INTO user (first_name)
VALUES (:first_name)
");
$stmt->bindParam(':first_name', $first_name);
$first_name = $user;
$stmt->execute();
}
}
$database = new Database("mysql:host=".DB_HOST.";dbname=".DB_NAME,DB_USER,DB_PASS);
$user = new User($database);
$user->register('name');
在我看来,连接应该在类中进行,因为连接是类的重点。它还使您能够进行异常处理。
根据我的经验,最好在拟合类中编写所有语句。就像我在上面的代码中所做的那样。
我希望这会对你有所帮助。