【问题标题】:Use PDO database class without creating new connection every time? [duplicate]使用 PDO 数据库类而不每次都创建新连接? [复制]
【发布时间】:2017-06-26 08:35:16
【问题描述】:

我得到了这个 PDO 数据库类

class clsDatabase{
  // db settings
  private $host   = 'localhost';
  private $user   = 'test';
  private $dbname = 'test';
  private $pass   = 'test1';

  private $dbh;
  private $error;

  public function __construct(){
        // Set DSN
        $dsn = 'mysql: host=' . $this->host . ';dbname=' . $this->dbname;
        // Set options
        $options = array(
            PDO::ATTR_PERSISTENT            => true,
            PDO::ATTR_ERRMODE               => PDO::ERRMODE_EXCEPTION,
            PDO::MYSQL_ATTR_INIT_COMMAND    => 'SET NAMES UTF8'
        );
        // Create a new PDO instanace
        try{
            $this->dbh = new PDO($dsn, $this->user, $this->pass, $options); 
        }
        // Catch any errors
        catch(PDOException $e){
            $this->error = $e->getMessage();
            echo $this->error;
            exit;
        }       
    }

    public function query($query){
        $this->stmt = $this->dbh->prepare($query);
    }
}   

我尝试将我的代码分成不同的类,例如我有一个连接到 clsUserController 的 clsDBUser。我这样做是为了知道什么类使用什么数据库代码。我的 clsDBUser 类看起来像这样

class clsDBUser extends clsDatabase {
    // construct
    public function __construct() {
        parent::__construct();
    }

    // get users
    public function getUsers($users_id){
        $query = "
            SELECT 
                email
            FROM 
                users
            WHERE 
               users_id = :users_id
        ";          
        $this->query($query);
        $this->bind(':users_id', $users_id);

        if($row = $this->single()){
            $this->close();
            return $row;
        }
        $this->close();
        return false;       
    }
}

我想知道这是要走的路还是我现在在每个班级都创建一个新的数据库连接?因为通常在 PHP4 中(是的,我知道旧的)我无法识别我每次都必须建立一个新的数据库连接。

我需要改进吗,我需要如何改进?

【问题讨论】:

标签: php mysql pdo


【解决方案1】:

不要从连接类 (clsDatabase) 扩展实体 (clsDBUser)。

clsDatabase 使用单例(或更高级的模式)。

例如:

class clsDatabase {

    static private $instance = null;

    // some other private fields

    private function __construct(/* parameters*/) {
        // do it
    }

    public static function instance() {
        if (is_null(self::$instance)) {
            self::$instance = new self(/* pass any parameters */);
        }
        return self::$instance;
    }

    public function queryRow($query) {
        $oStatement = $this->dbh->prepare($query);

        // ...

        return $row;
    }
}

class clsDBUser {

    public function getUser($id) {
        $query = "...";
        return $clsDatabase::instance()->queryRow($query);
    }

}

【讨论】:

  • 单例是一种反模式,你应该避免它。你想要的是 DI (en.wikipedia.org/wiki/Dependency_injection)。你可以 php-di.它是一个 DIC(依赖注入容器),可自动连接您的依赖项
  • 是的,DI 是正确的解决方案,但是“不要扩展”是正确的答案。我写过:“或者更高级的模式”。
  • 好的,我可以用你的例子吗?因为我明天要试试这个,如果可以的话,可能会向你提出更多问题
  • @Dávid Horváth 你能给我一个完整的例子吗?
  • @poNgz0r 请参阅我的其他答案以获取高级示例。
【解决方案2】:

这里有三层:

  • 数据库连接器:您可以为此使用纯 PDO 或数据库抽象层库 (Doctrine DBAL)
  • 实体存储库:换句话说,某种 ORM。 Doctrine 提供高级 ORM 功能。当然,您可以编写自己的轻量级解决方案。
  • 实体:可以是简单的CRUDActiveRecord 或逻辑记录的任何其他对象表示。

当我们手动执行此操作时...首先,不要扩展它们彼此。一般来说:永远不要从另一个扩展不同的层。请改用Dependency Injection (DI)。

当您将所有特定信息(依赖项)作为构造函数参数传递时,这是一个非常简单的 DI 案例。我的类似活动对象的示例Entity 只知道实体的一般行为方式(在存储库中的键处)。为简单起见,我使用原始 SQL。

存储库类:

class Repository {

    private $oPDO;
    private $tableName;
    private $keyFieldName;

    public function __construct($oPDO, $tableName, $keyFieldName) {
        $this->oPDO = $oPDO;
        $this->tableName = $tableName;
        $this->keyFieldName = $keyFieldName;
    }

    public function getPDO() {
        return $this->oPDO;
    }

    public function getTableName() {
        return $this->tableName;
    }

    public function getKeyFieldName() {
        return $this->keyFieldName;
    }

    public function getEntity($id) {
        return new Entity($this, $id);
    }

    public function createEntity() {
        return new Entity($this, null);
    }

}

实体类:

class Entity implements ArrayAccess {

    private $oRepository;
    private $id;

    private $record = null;

    public function __construct($oRepository, $id) {
        $this->oRepository = $oRepository;
        $this->id = $id;
    }

    public function load($reload = false) {
        if (!$this->record && !$this->id) {
            return false;
        }

        if (!$reload && !is_null($this->record)) {
            return true;
        }

        $quotedTableName = $this->quoteIdentifier($this->oRepository->getTableName());
        $quotedKeyFieldName = $this->quoteIdentifier($this->oRepository->getKeyFieldName());
        $selectSql = "SELECT * FROM {$quotedTableName} WHERE {$quotedKeyFieldName} = ?";
        $oStatement = $this->oRepository->getPDO()->prepare($selectSql);
        $this->bindParam($oStatement, 1, $this->id);
        $oStatement->execute();

        $result = $oStatement->fetch(PDO::FETCH_ASSOC);

        if ($result === false || is_null($result)) {
            return false;
        }

        $this->record = $result;
        return true;
    }

    public function save() {
        $oPDO = $this->oRepository->getPDO();

        $tableName = $this->oRepository->getTableName();
        $keyFieldName = $this->oRepository->getKeyFieldName();
        $quotedTableName = $this->quoteIdentifier($tableName);
        $quotedKeyFieldName = $this->quoteIdentifier($keyFieldName);

        if (is_null($this->id)) {
            $insertSql = "INSERT INTO {$quotedTableName} (";
            $insertSql .= implode(", ", array_map([$this, "quoteIdentifier"], array_keys($this->record)));
            $insertSql .= ") VALUES (";
            $insertSql .= implode(", ", array_fill(0, count($this->record), "?"));
            $insertSql .= ")";
            $oStatement = $oPDO->prepare($insertSql);

            $p = 1;
            foreach ($this->record as $fieldName => $value) {
                $this->bindParam($oStatement, $p, $value);
                $p++;
            }

            if ($oStatement->execute()) {
                $this->id = $oPDO->lastInsertId();
                return true;
            } else {
                return false;
            }
        } else {
            $updateSql = "UPDATE {$quotedTableName} SET ";
            $updateSql .= implode(" = ?, ", array_map([$this, "quoteIdentifier"], array_keys($this->record)));
            $updateSql .= " = ? WHERE {$quotedKeyFieldName} = ?";
            $oStatement = $oPDO->prepare($updateSql);

            $p = 1;
            foreach ($this->record as $fieldName => $value) {
                $this->bindParam($oStatement, $p, $value);
                $p++;
            }
            $this->bindParam($oStatement, $p, $this->id);

            if ($oStatement->execute()) {
                if (isset($this->record[$keyFieldName])) {
                    $this->id = $this->record[$keyFieldName];
                }
                return true;
            } else {
                return false;
            }
        }
    }

    public function isExisting($reload = false) {
        if (!$this->record && !$this->id) {
            return false;
        }

        if (!$reload && !is_null($this->record)) {
            return true;
        }

        $quotedTableName = $this->quoteIdentifier($this->oRepository->getTableName());
        $quotedKeyFieldName = $this->quoteIdentifier($this->oRepository->getKeyFieldName());
        $selectSql = "SELECT 1 FROM {$quotedTableName} WHERE {$quotedKeyFieldName} = ?";
        $oStatement = $this->oRepository->getPDO()->prepare($selectSql);
        $oStatement->bindParam(1, $this->id);
        $oStatement->execute();

        $result = $oStatement->fetch(PDO::FETCH_ASSOC);

        if ($result === false || is_null($result)) {
            return false;
        }

        return true;
    }

    public function getId() {
        return $this->id;
    }

    public function getRecord() {
        $this->load();
        return $this->record;
    }

    public function offsetExists($offset) {
        $this->load();
        return isset($this->record[$offset]);
    }

    public function offsetGet($offset) {
        $this->load();
        return $this->record[$offset];
    }

    public function offsetSet($offset, $value) {
        $this->load();
        $this->record[$offset] = $value;
    }

    public function offsetUnset($offset) {
        $this->load();
        $this->record[$offset] = null;
    }

    private function quoteIdentifier($name) {
        return "`" . str_replace("`", "``", $name) . "`";
    }

    private function bindParam($oStatement, $key, $value) {
        $oStatement->bindParam($key, $value);
    }

}

用法:

$oRepo = new Repository($oPDO, "user", "user_id");

var_dump($oRepo->getEntity(2345235)->isExisting());

$oSameUser = $oRepo->getEntity(1);
var_dump($oSameUser->isExisting());
var_dump($oSameUser->getRecord());

$oNewUser = $oRepo->createEntity();
$oNewUser["username"] = "smith.john";
$oNewUser["password"] = password_hash("ihatesingletons", PASSWORD_DEFAULT);
$oNewUser["name"] = "John Smith";
$oNewUser->save();

$oNewUser["name"] = "John Jack Smith";
$oNewUser->save();

当然,您可以使用特定行为从 Repository 扩展 MoreConcreteRepository 和从 Entity 扩展 MoreConcreteEntity

【讨论】:

    【解决方案3】:

    您应该走 mr.void 的答案中显示的道路。简而言之:

    1. 摆脱 clsDatabase。
    2. 创建一个 PDO 实例。
    3. 将其传递到 clsDBLogin 的属性中,就像 mr.void 的答案中显示的那样。
    4. 然后以$this->db->prepare()等形式使用这个pdo实例

    所以应该是这样的

    class clsDBLogin
    {
        public function __construct($db)
        {
            $this->db = $db;
        }
    
        public function validateLogin($email)
        {  
            $email = trim($email);
    
            // Check user in db to start verification
            $query = 'SELECT * FROM users u, users_info ui 
                      WHERE u.users_id = ui.users_id AND u.email = ?';
            $stmt = $this->db->prepare($query);
            $stmt->execute([$email]);
            return $stmt->fetch();
        }
    }
    
    $dsn = 'mysql: host=localhost;dbname=test;charset=utf8';
    $options = array(
            PDO::ATTR_PERSISTENT            => true,
            PDO::ATTR_ERRMODE               => PDO::ERRMODE_EXCEPTION,
    );
    // Create a new PDO instanace
    $pdo = new PDO($dsn, $this->user, $this->pass, $options); 
    
    $DBLogin = new clsDBLogin($pdo);
    $user = $DBLogin->validateLogin($email);
    

    【讨论】:

      【解决方案4】:

      重建:

      clsDB 类只有连接的东西

      class clsDB{
          // db settings
          private $host   = 'localhost';
          private $user   = 'test';
          private $dbname = 'test';
          private $pass   = 'test';
      
          private $dbh;
          private $error;
      
          public function __construct(){
              // Set DSN
              $dsn = 'mysql: host=' . $this->host . ';dbname=' . $this->dbname;
              // Set options
              $options = array(
                  PDO::ATTR_PERSISTENT            => true,
                  PDO::ATTR_ERRMODE               => PDO::ERRMODE_EXCEPTION,
                  PDO::MYSQL_ATTR_INIT_COMMAND    => 'SET NAMES UTF8'
              );
              // Create a new PDO instanace
              try{
                  $this->dbh = new PDO($dsn, $this->user, $this->pass, $options); 
              }
              // Catch any errors
              catch(PDOException $e){
                  $this->error = $e->getMessage();
                  echo $this->error;
                  exit;
              }       
          }           
      }
      

      clsDBLogin:

      class clsDBLogin{   
         private $db;
      
         public function __construct($db) {
             $this->db = $db;
         }
      }
      

      在 index.php 我做:

      $clsDB      = new clsDB();
      $clsDBLogin = new clsDBLogin($clsDB);
      

      在 clsDBLogin 中我会这样做:

      public function validateLogin($email){  
          $email = str_replace(' ', '', $email);
          $email = strtolower($email);
      
          // Check user in db to start verification
          $query = '
              SELECT
                  u.*, ui.*
              FROM
                  users as u,
                  users_info as ui
              WHERE
                  u.users_id = ui.users_id
              AND
                  u.email = :email                
          ';
          $this->db->prepare($query);
          $this->db->bindValue(':email', $email, PDO::PARAM_STR);
      
          if($this->db->execute()){
              if($row = $this->db->fetch(PDO::FETCH_ASSOC)){
                  return $row;            
              }       
          }   
      }
      

      【讨论】:

      • 不完全。您的代码非常混乱(clsDatabase::getInstance() 到处都是),令人困惑(使用查询作为准备的别名,我称之为破坏)和不灵活(此代码将坚持单个实例,而您将无法使用另一个)。此外,关闭连接后,您将无法在脚本中运行多个查询,这相当荒谬
      • 好吧,那接下来该怎么走?
      • 1.摆脱 clsDatabase。 2. 创建 PDO 的实例。 3. 将它传递到 clsDBLogin 的属性中,就像 mr.void 的答案中显示的那样。 4.然后以$this->db->prepare()等形式使用这个pdo实例
      • 这个基本上更好。虽然还是有很多没用的代码,但总的来说方法还可以
      【解决方案5】:

      嘿,我会做这样的事情

      class DB {
         // connectionStuff goes Here
      }
      
      class Model {
         private $db
      
         public function __construct($db) {
             $this->db = $db;
         }
      }
      

      用途:

      $db = new DB("your connection stuff goes here");
      
      
      $model = new Model($db);
      $userModel = new UserModel($db);
      $anotherModel = new AnotherModel($db);
      

      【讨论】:

      • 不正是这个问题吗?因为在这个 sn-p 中,您每次都会在所有类中的每个操作之前调用$db = new DB.. - 这不是效率低下吗?
      • @TheGodfather 不,在最初的问题中,为继承父类的对象的每个实例建立了连接。在我的示例中,只有一个用于创建连接的实例,该实例将传递给依赖于 sql 连接的所有实例
      猜你喜欢
      • 2014-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-24
      • 2011-08-25
      • 2013-08-03
      • 1970-01-01
      相关资源
      最近更新 更多