PDO和依赖注入

时间:2017-01-25 21:55:32

标签: php mysql pdo dependency-injection

我试图找出在不使用单例方法的情况下将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类中创建一个函数来插入数据库。任何帮助表示赞赏。

3 个答案:

答案 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');

在我看来,连接应该在类中进行,因为连接是类的重点。它还使您能够进行异常处理。

根据我的经验,最好在拟合类中编写所有语句。就像我在上面的代码中所做的那样。

我希望这会对你有所帮助。