【问题标题】:Insert/update helper function using PDO使用 PDO 插入/更新辅助函数
【发布时间】:2011-04-15 23:06:18
【问题描述】:

我有一个非常简单的辅助函数来为传统的普通 mysql 驱动程序使用生成 SET 语句:

function dbSet($fields) {
  $set='';
  foreach ($fields as $field) {
    if (isset($_POST[$field])) {
      $set.="`$field`='".mysql_real_escape_string($_POST[$field])."', ";
    }
  }
  return substr($set, 0, -2); 
}

这样使用

$id = intval($_POST['id']);
$fields = explode(" ","name surname lastname address zip fax phone");
$_POST['date'] = $_POST['y']."-".$_POST['m']."-".$_POST['d'];
$query  = "UPDATE $table SET ".dbSet($fields)." stamp=NOW() WHERE id=$id";

它使代码非常干爽、简单但同时又很灵活。

我想问是否有人愿意分享类似的功能,利用 PDO 准备语句功能?

我仍然在怀疑,如何做到这一点。
有没有直接简单的方法来使用 PDO 准备语句插入数据? 应该是什么形式?查询生成器助手?还是插入查询助手?它应该采用什么参数?

我希望它可以很容易地在 SO 上用作答案。因为在每个主题中我们都可以看到prepared statements的用法推荐,但是没有一个好的例子。我的意思是,现实生活中的例子。我认为键入 20 次 bind_param() 并不是一种好的编程风格。 甚至还有 20 个问号。

【问题讨论】:

  • @Col:您到底在寻找什么?只有更新的东西?还是各种占位符? My DB wrapper 支持整数、字符串和数组的 ?i, ?s, ?a 占位符。我不知道这是否适合你。
  • 你愿意给不同的数据库抽象类/ORM系统,而不是你通常的标准php函数。
  • 你真的还在使用 mysql lib - 甚至没有 mysqli 吗?为什么?
  • @Col:它被认为是过时的。 @Mark:嗯,你可以看到他目前正在转向 PDO。所以真的不需要那个评论!
  • @Col。弹片 有趣,请引导我到他说的地方。并不是说我认为一个人是编程的最终权威,现在,如果 Jon SKEET 这么说的话……而且,我的 cmets 的意义 - 你的意义是什么?您假设我在 Windows 上运行网络服务器(我没有,我是 Debian 管理员)只是因为我指出 php 的 mysql 库几乎已过时。顺便说一句,你有没有学过 PHP 以外的语言?

标签: php mysql pdo


【解决方案1】:

我通常有一个扩展 PDO 的类,但我的类是非常自定义的。如果我把它清理干净并测试了,我会在稍后发布。不过,这是您系统的解决方案。

function dbSet($fields, &$values) {
    $set = '';
    $values = array();

    foreach ($fields as $field) {
        if (isset($_POST[$field])) {
            $set .= "`$field` = ?,";
            $values[] = $_POST[$field];
        }
    }

    return rtrim($set, ',');
}

$fields = explode(" ","name surname lastname address zip fax phone date");
$_POST['date'] = $_POST['y']."-".$_POST['m']."-"$_POST['d'];

$query  = "UPDATE $table SET ".dbSet($fields, $values).", stamp=NOW() WHERE id=?";
$values[] = $id;

$dbh->prepare($query);
$dbh->execute($values);  

这可能并不完美,可以使用调整。考虑到$dbh 是使用 PDO 连接设置的。等待我提出的任何小语法问题,应该可以。

编辑

真的,我想我会选择 Doctrine ORM(或其他 ORM)。当您设置模型并在那里添加所有验证时,它就像这样简单:

$table = new Table();
$table->fromArray($_POST);
$table->save();

这应该很容易填充内容。这当然是使用 ORM,比如 Doctrine。

更新

对第一个代码进行了一些小的调整,例如将isset 放回去并使用rtrim 覆盖substr。想要提供一个 PDO 扩展类的模型,只需要布局实现它的方式并进行一些单元测试以确保它可以正常工作。

【讨论】:

  • 我还在努力掌握PDO,但是你不需要绑定变量吗?谁能给我解释一下?
  • 谢谢,我有这样的想法。是的,我们还必须准备 $values 数组。
  • @catfish,设置要在execute 函数中使用的值数组可以减少绑定参数的需要,如果您使用?,我无法与:variable 交谈,因为我从来没有用过那种方法。数组的顺序很重要,所以要小心。
  • @Catfish 他们被绑定了。我们得到一个像 UPDATE users SET username=?, password=? stamp=NOW() WHERE id=? 这样的查询和包含 3 个元素的 $values 数组。然后将此数组绑定到此查询
  • 为什么把上面的isset改成empty?例如,人们确实希望将值更改为空字符串。
【解决方案2】:

参考:How can I prevent SQL injection in PHP?

$preparedStatement = $db->prepare('SELECT * FROM employees WHERE name = :name');
$preparedStatement->execute(array(':name' => $name));
$rows = $preparedStatement->fetchAll();

【讨论】:

  • 我认为他正在寻找一种固定的方式来修改他当前的代码以使用 PDO,而不是对如何使用 PDO 做出“千篇一律”的回应。
  • 为什么要重新定义轮子?只需要三行
  • 我自己可以找到虚拟示例。但这些例子并不适用于现实生活。每个字段名写三遍不是我梦寐以求的工作。
  • @premiso 谢谢,但我不只是在寻找确切的替代品。我也愿意接受任何其他建议,只要它可以亲吻和干燥 :)
【解决方案3】:

我会扩展核心 PDO 类并使用如下方法:

class Database extends PDO
{
    public function QueryFromPost($Query,$items)
    {
        $params = array();
        $Query .= ' WHERE ';

        foreach($items as $key => $default)
        {
             $Query .= ' :' . $key. ' = ' . $key;
             if(isset($_POST[$key]))
             {
                  $params[':' . $key] = $_POST[$key];
             }else
             {
                 $params[':' . $key] = $default;
             }
        }
        $s = $this->prepare($Query);
        return $s->execute($params);
    }
}

然后像这样使用

$db = new Database(/*..Default PDO Params*/);
$statement = $db->QueryFromPost('SELECT * FROM employees',array('type' => 'plc'));
foreach($preparedStatement->fetchAll() as $row)
{
    //...
}

但是正如已经说过的那样,您应该非常厌倦您尝试做的事情,您需要验证您的数据,它已被清理但您尚未验证。

【讨论】:

  • 嗯,这是 SELECT 查询,但我对 INSERT 查询自动化很感兴趣。我通过使用具有允许字段名称的数组来验证输入。看来这是唯一可靠的方法了。
  • 您没有使用框架或任何特定模式吗?
  • 我愿意使用一些框架,如果我觉得它合理且可用的话。
  • 好吧,我会建议CodeIgnighter,但我真的很讨厌它的核心加载结构,你有没有想过自己做一个,它不是很困难?
【解决方案4】:

对于我认为经常出现的参数绑定案例,我一直在修补一些琐碎的事情。 http://fossil.include-once.org/hybrid7/wiki/db

无论如何;它提供了一些alternative prepared statement placeholders。您的示例可以缩短为:

db("UPDATE table SET :, WHERE id=:id", $columns[], $where[]);

这种情况只适用于命名参数,因此 $set 将是 array("name"=>..) 和 $where=array("id"=>123)。 :, 在您传递的第一个数组上展开。它已替换为 逗号 分隔的 name=:name 对(这就是为什么它的助记符是 :,)。

还有几个占位符 :, :& :::? 用于不同的用例。只有?? 确实有点标准。所以它需要一些习惯,但它显着简化了准备好的语句和数组绑定(PDO 本身不这样做)。

【讨论】:

  • 哦,太好了。我会看看。我在想这种东西,因为标准的占位符集很差。想象一下,您需要从某个数组中创建一个 IN 语句。使用特殊占位符可以轻松自动化的手工操作过多。
  • 没错。这是驱使我使用它的原始用例。从那时起,我就一直在爬行它。 ?? 是最通用的,这就是 Perls DBIx 也使用它的原因。字符串修补似乎是 LINQ 语言支持的第二好的替代方案。
  • 很棒的概念和很好的实现。下周我会更仔细地研究它。就我个人而言,我只有一个帮助器,它为上面的 @premiso 所做的查询创建占位符,但这更优雅,并且在代码中使 sql 更具可读性。
【解决方案5】:

你可以像这样扩展 PDO:

class CustomPDO extends PDO {

    public function updateTable($sTable, array $aValues = array()){

        if (!empty($aValues) && !empty($sTable)){

            # validation of table / columns name
            $sTable = mysql_real_escape_string($sTable);

            $aColumns = array_map('mysql_real_escape_string',array_keys($aValues));

            $aElements = array();

            foreach ($aColumns as $sColumn){

                $aElements[] = "`$sColumn`= :$sColumn";

            } // foreach

            $sStatement = "UPDATE $sTable SET " . implode(',', $aElements);

            $oPDOStatement = $this->prepare($sStatement);

            if ($oPDOStatement){

                return $oPDOStatement->execute($aValues);

            } // if

        } // if

        return false;

    } // updateTable

}

# usage :
# $oDb->updateTable('tbl_name',$_POST);


# test

error_reporting (E_ALL);
ini_Set('display_errors',1);

$oDb = new CustomPDO('sqlite::memory:');

$oDb->exec('CREATE TABLE t1(c1 TEXT, c2 INTEGER)');

$oDb->exec("INSERT INTO t1(c1, c2) VALUES ('X1',1)");

var_dump($oDb->query('SELECT * FROM t1')->fetchAll(PDO::FETCH_ASSOC));

$oDb->updateTable('t1', array('c1'=>'f1','c2**2'=>2));

var_dump($oDb->query('SELECT * FROM t1')->fetchAll(PDO::FETCH_ASSOC));

【讨论】:

    【解决方案6】:

    与其他人一样,我扩展了标准 PDO 类以满足我的需要。类似的东西可能适合你:

    Class ExtendedPDO extends PDO
    {
    
        public function prepareArray($sql, array $data)
        {
            // Call the standard prepare method
            $statement = parent::prepare($sql);
    
            foreach ($data as $field=>$value) {
                $statement->bindValue(':' . $field, $value);
            }
    
            return $statement;
        }
    
    }
    

    那么你可以很简单地使用它:

    // Include connection variables
    include '../includes/config/database.php';
    
    // The data to use in the query
    $data = array(
        'title' => 'New value',
        'id'    => 1,
    );
    
    // The query you want to run
    $sql = '
        UPDATE
            test
        SET
            title = :title
        WHERE
            id = :id
    ';
    
    try {
        // Connect to the database
        $dbh = new ExtendedPDO(PDO_DSN, PDO_USERNAME, PDO_PASSWORD);
    
        // Attach the data to your query
        $stmt = $dbh->prepareArray($sql, $data);
    
        // Run it
        $stmt->execute();
    } catch (PDO Exception $e) {
        echo $e->getMessage();
    }
    

    【讨论】:

      【解决方案7】:

      这是我的通用数据库抽象类。看看autoExecute() 函数。它为您可能想要完成的任何事情提供了大量的灵活性。我应该警告说这是为 PHP 5.3 编写的,并且为 PostgreSQL 稍微定制了一些。

      <?php
      /**
       * Database abstraction and query result classes
       * Requires PHP 5.3
       *
       * Events:
       *  - on_commit - Dispatched when the transaction is successfully committed to the DB
       *  - on_rollback - Dispatched when the transaction is rolled back in the DB
       *
       * @author Kenaniah Cerny <kenaniah@gmail.com>
       * @version 1.1.2
       * @license http://creativecommons.org/licenses/by/3.0/us/
       * @copyright Copyright (c) 2009, Kenaniah Cerny
       */
      class Database extends PDO {
      
          private $stmt;
          private $good_trans = null;
          private $nested_transactions = 0; //Keeps track of virtual transaction nesting level
          private $callbacks = array();
      
          private static $connections = array(); //Keeps track of opened connections
      
          /**
           * Returns a database instance using lazy instantiation
           * @param string $name a database connection name
           * @param array $config database config details for a new connection
           */
          static function getInstance($name = 'main', $config=array()){
      
              //Attempt to return an existing connection
              if(array_key_exists($name, self::$connections)):
                  return self::$connections[$name];
              endif;
      
              //Attempt to create a new connection
              $host = in_array($config['host'], array('localhost', '127.0.0.1')) ? "" : ";host=" . $config['host'];
              $db = new Database($config['driver'].":dbname=".$config['name'].$host, $config['user'], $config['pass']);
      
              //Save to connection pool
              self::$connections[$name] = $db;
      
              return $db;
      
          }
      
          /**
           * Registers a callback to be run when the given event is invoked
           * @param string $event Event name
           * @param callable $callable
           */
          public function register_listener($event, $callable){
      
              if(!array_key_exists($event, $this->callbacks)):
                  $this->callbacks[$event] = array($callable);
              else:
                  $this->callbacks[$event][] = $callable;
              endif;
      
          }
      
          /**
           * Invokes callbacks for the given event type
           * @param string $event Event name
           * @param boolean $stop_on_false Stops bubbling this event if one of the handlers returns false
           */
          protected function dispatch_event($event, $stop_on_false = true){
      
              if(!array_key_exists($event, $this->callbacks)) return;
      
              foreach($this->callbacks[$event] as $callable):
      
                  $res = call_user_func($callable, $this, $event);
                  if($stop_on_false && $res === false) return false;
      
              endforeach;
      
              return true;
      
          }
      
          /**
           * PDO Constructor
           * @param $dsn
           * @param $username
           * @param $password
           */
          function __construct($dsn, $username, $password) {
              parent::__construct($dsn, $username, $password);
          }
      
          /**
           * Prepares an SQL statement
           * @param string $sql
           */
          function prepare($sql) {
              $stmt = parent::prepare($sql, array(PDO::ATTR_STATEMENT_CLASS => array(__NAMESPACE__.'\DatabaseStatement')));
              $stmt->setFetchMode(PDO::FETCH_ASSOC);
              return $stmt;
          }
      
          /**
           * Prepares an executes an SQL statement with the parameters provided
           * @param string $sql
           * @param array $params
           */
          function execute($sql, $params = array()) {
      
              if($this->debug):
                  var_dump("Statement:\n".$sql."\nParams: ".$this->fmt($params));
              endif;
      
              try {
                  $stmt = $this->prepare($sql);
                  $val = $stmt->execute((array) $params);
                  if($stmt->errorCode() != '00000') error_log($this->errormsg());
                  if($this->debug && $stmt->errorCode() != '00000'){
                      var_dump($stmt->errorInfo());
                      Errors::add("Database error: ".$this->errormsg(), E_USER_ERROR);
                  }
                  if(!$val) return false;
              } catch (PDOException $e){
                  if($this->debug) var_dump($stmt->errorInfo());
                  error_log($this->errormsg());
                  Errors::add("Database error: ".$this->errormsg(), E_USER_ERROR);
                  if($this->nested_transactions) $this->failTrans();
                  else throw $e;
              }
      
              $this->stmt = $stmt;
      
              return $stmt;
      
          }    
      
          /**
           * Returns the value of the first column of the first row
           * of the database result.
           * @param $sql
           * @param $params
           */
          function getOne($sql, $params = array()){
              $stmt = $this->execute($sql, $params);
              return $stmt ? $stmt->getOne() : false;
          }
      
          /**
           * Fetches a single column (the first column) of a result set
           * @param $sql
           * @param $params
           */
          function getCol($sql, $params = array()){
              $stmt = $this->execute($sql, $params);
              return $stmt ? $stmt->getCol() : false;
          }
      
          /**
           * Fetches rows in associative array format
           * @param $sql
           * @param $params
           */
          function getAssoc($sql, $params = array()){
              $stmt = $this->execute($sql, $params);
              return $stmt ? $stmt->getAssoc() : false;
          }
      
          /**
           * Fetches rows in array format with columns
           * indexed by ordinal position
           * @param $sql
           * @param $params
           */
          function getArray($sql, $params = array()){
              $stmt = $this->execute($sql, $params);
              return $stmt ? $stmt->getArray() : false;
          }
      
          /**
           * Fetches all rows in associative array format
           * @param $sql
           * @param $params
           */
          function getAll($sql, $params = array()){
              return $this->getAssoc($sql, $params);
          }
      
          /**
           * Fetches rows in array format where the first column
           * is the key name and all other columns are values
           * @param $sql
           * @param $params
           */
          function getKeyPair($sql, $params = array()){
              $stmt = $this->execute($sql, $params);
              return $stmt ? $stmt->getKeyPair() : false;
          }
      
          /**
           * Fetches rows in multi-dimensional format where the first
           * column is the key name and all other colums are grouped
           * into associative arrays for each row
           * @param $sql
           * @param $params
           */
          function getGroup($sql, $params = array()){
              $stmt = $this->execute($sql, $params);
              return $stmt ? $stmt->getGroup() : false;
          }
      
          /**
           * Fetches only the first row and returns it as an
           * associative array
           * @param $sql
           * @param $params
           */
          function getRow($sql, $params = array()){
              $stmt = $this->execute($sql, $params);
              return $stmt ? $stmt->getRow() : false;
          }
      
          /**
           * Internal function used for formatting parameters in debug output
           * @param unknown_type $params
           */
          private function fmt($params){
              $arr = array();
              foreach((array) $params as $k=>$v){
                  if(is_null($v)) $v = "NULL";
                  elseif(is_bool($v)) $v = $v ? "TRUE" : "FALSE";
                  $arr[] = "[".$k."] => ".$v;
              }
              return "Array(".join(", ", $arr).")";
          }
      
          /**
           * Returns the number of affected rows from an executed statement
           */
          function affected_rows(){
              return $this->stmt ? $this->stmt->rowcount() : false;
          }
      
          /**
           * Automated statement processing
           *
           * Params array takes the following fields:
           *
           *  - table         The name of the table to run the query on
           *
           *  - data          A key-value paired array of table data
           *
           *  - mode          INSERT, UPDATE, REPLACE, or NEW
           *
           *  - where         Can be a string or key-value set. Not used on INSERTs
           *                  If key-value set and numerically indexed, uses values from data
           *                  If key-value and keys are named, uses its own values
           *
           *  - params        An array of param values for the where clause
           *
           *  - returning     Optional string defining what to return from query.
           *                  Uses PostgreSQL's RETURNING construct
           *
           *  This method will return either a boolean indicating success, an array
           *  containing the data requested by returning, or a boolean FALSE indicating
           *  a failed query.
           *
           */
          function autoExecute($table, $params, $data){
      
              $fields = array(); //Temp array for field names
              $values = array(); //Temp array for field values
              $set = array(); //Temp array for update sets
              $ins = array(); //Insert value arguments
      
              $params['table'] = $table;
              $params['data'] = $data;
      
              $params['params'] = (array) $params['params'];
      
              //Parse the data set and prepare it for different query types
              foreach((array) $params['data'] as $field => $val):
      
                  $fields[] = $field;
                  $values[] = $val;
                  $ins[] = "?";
                  $set[] = $field . " = ?";
      
              endforeach;
      
              //Check for and convert the array/object version of the where clause param
              if(is_object($params['where']) || is_array($params['where'])):
      
                  $clause = array();
                  $params['params'] = array(); //Reset the parameters list
      
                  foreach($params['where'] as $key => $val):
      
                      if(is_numeric($key)):
                          //Numerically indexed elements use their values as field names
                          //and values from the data array as param values
                          $field = $val;
                          $params['params'][] = $params['data'][$val];
                      else:
                          //Named elements use their own names and values
                          $field = $key;
                          $params['params'][] = $val;
                      endif;
      
                      $clause[] = $field . " = ?";
      
                  endforeach;
      
                  $params['where'] = join(" AND ", $clause);
      
              endif;
      
              //Figure out what type of query we want to run
              $mode = strtoupper($params['mode']);
              switch($mode):
                  case 'NEW':
                  case 'INSERT':
      
                      //Build the insert query
                      if(count($fields)):
                          $sql =  "INSERT INTO " . $params['table']
                                  . " (" . join(", ", $fields) . ")"
                                  . " SELECT " . join(", ", $ins);
                      else:
                          $sql =  "INSERT INTO " . $params['table']
                                  . " DEFAULT VALUES";
                      endif;
      
                      //Do we need to add a conditional check?
                      if($mode == "NEW" && count($fields)):
                          $sql .= " WHERE NOT EXISTS ("
                                  . " SELECT 1 FROM " . $params['table']
                                  . " WHERE " . $params['where']
                                  . " )";
                          //Add in where clause params
                          $values = array_merge($values, $params['params']);
                      endif;
      
                      //Do we need to add a returning clause?
                      if($params['returning']):
                          $sql .= " RETURNING " . $params['returning'];
                      endif;
      
                      //Execute our query
                      $result = $this->getRow($sql, $values);
      
                      //Return our result
                      if($params['returning']):
                          return $result;
                      else:
                          return $result !== false;
                      endif;
      
                      break;
                  case 'UPDATE':
      
                      if(!count($fields)) return false;
      
                      //Build the update query
                      $sql =  "UPDATE " . $params['table']
                              . " SET " . join(", ", $set)
                              . " WHERE " . $params['where'];
      
                      //Do we need to add a returning clause?
                      if($params['returning']):
                          $sql .= " RETURNING " . $params['returning'];
                      endif;
      
                      //Add in where clause params
                      $values = array_merge($values, $params['params']);
      
                      //Execute our query
                      $result = $this->getRow($sql, $values);
      
                      //Return our result
                      if($params['returning']):
                          return $result;
                      else:
                          return $result !== false;
                      endif;
      
                      break;
                  case 'REPLACE': //UPDATE or INSERT
      
                      //Attempt an UPDATE
                      $params['mode'] = "UPDATE";
                      $result = $this->autoExecute($params['table'], $params, $params['data']);
      
                      //Attempt an INSERT if UPDATE didn't match anything
                      if($this->affected_rows() === 0):
                          $params['mode'] = "INSERT";
                          $result = $this->autoExecute($params['table'], $params, $params['data']);
                      endif;
      
                      return $result;
      
                      break;
                  case 'DELETE':
      
                      //Don't run if we don't have a where clause
                      if(!$params['where']) return false;
      
                      //Build the delete query
                      $sql =  "DELETE FROM " . $params['table']
                              . " WHERE " . $params['where'];
      
                      //Do we need to add a returning clause?
                      if($params['returning']):
                          $sql .= " RETURNING " . $params['returning'];
                      endif;
      
                      //Execute our query
                      $result = $this->getRow($sql, $params['params']);
      
                      //Return our result
                      if($params['returning']):
                          return $result;
                      else:
                          return $result !== false;
                      endif;
      
                      break;
                  default:
                      user_error('AutoExecute called incorrectly', E_USER_ERROR);
                      break;
              endswitch;
      
          }
      
          /**
           * @see $this->startTrans()
           */
          function beginTransaction(){
              $this->startTrans();
          }
      
          /**
           * Starts a smart transaction handler. Transaction nesting is emulated
           * by this class.
           */
          function startTrans(){
      
              $this->nested_transactions++;
              if($this->debug) var_dump("Starting transaction. Nesting level: " . $this->nested_transactions);
      
              //Do we need to begin an actual transaction?
              if($this->nested_transactions === 1):
                  parent::beginTransaction();
                  $this->good_trans = true;
                  $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
              endif;
      
          }
      
          /**
           * Returns TRUE if the transaction will attempt to commit, and
           * FALSE if the transaction will be rolled back upon completion.
           */
          function isGoodTrans(){
              return $this->good_trans;
          }
      
          /**
           * Marks a transaction as a failure. Transaction will be rolled back
           * upon completion.
           */
          function failTrans(){
              if($this->nested_transactions) $this->good_trans = false;
              if($this->debug):
                  Errors::add("Database transaction failed: ".$this->errorMsg());
              endif;
              $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
          }
      
          /**
           * @see $this->rollbackTrans()
           */
          function rollback(){
              $this->rollbackTrans();
          }
      
          /**
           * Rolls back the entire transaction and completes the current nested
           * transaction. If there are no more nested transactions, an actual
           * rollback is issued to the database.
           */
          function rollbackTrans(){
              if($this->nested_transactions):
                  $this->nested_transactions--;
                  if($this->debug) var_dump("Rollback requested. New nesting level: " . $this->nested_transactions);
                  $this->good_trans = false;
                  if($this->nested_transactions === 0):
                      $this->good_trans = null;
                      $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
                      if($this->debug) var_dump("Transaction rolled back.");
                      parent::rollback();
                      $this->dispatch_event('on_rollback');
                  endif;
              endif;
          }
      
          /**
           * Clears the nested transactions stack and issues a rollback to the database.
           */
          function fullRollback(){
              while($this->nested_transactions) $this->rollbackTrans();
          }
      
          /**
           * Returns the number of nested transactions:
           * 0 - There is no transaction in progress
           * 1 - There is one transaction pending
           * >1 - There are nested transactions in progress
           */
          function pending_trans(){
              return $this->nested_transactions;
          }
      
          /**
           * @see $this->completeTrans()
           */
          function commit($fail_on_user_errors = false){
              return $this->completeTrans($fail_on_user_errors);
          }
      
          /**
           * Completes the current transaction and issues a commit or rollback to the database
           * if there are no more nested transactions. If $fail_on_user_errors is set, the
           * transaction will automatically fail if any errors are queued in the Errors class.
           * @param boolean $fail_on_user_errors
           */
          function completeTrans($fail_on_user_errors = false){
      
              if(!$this->nested_transactions) return;
      
              //Fail the transaction if we have user errors in the queue
              if($fail_on_user_errors && Errors::exist()) $this->good_trans = false;
      
              //Do we actually need to attempt to commit the transaction?
              if($this->nested_transactions === 1):
      
                  if(!$this->good_trans || !parent::commit()){
                      if($this->debug) var_dump("Transaction failed: " . $this->errormsg());
                      $this->rollbackTrans();
                      return false;
                  }
      
                  //Transaction was good
                  $this->nested_transactions--;
                  $this->good_trans = null;
                  $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
                  if($this->debug) var_dump("Transaction committed.");
                  $this->dispatch_event('on_commit', false);
                  return true;
              else:
                  //Don't take action just yet as we are still nested
                  $this->nested_transactions--;
                  if($this->debug) var_dump("Virtual commit. New nesting level: " . $this->nested_transactions);
              endif;
      
              return $this->good_trans;
      
          }
      
          /**
           * Returns the text of the most recently encountered error
           */
          function errormsg(){
              $msg = $this->errorInfo();
              return $msg[2];
          }
      
      }
      
      class DatabaseStatement extends \PDOStatement implements \Countable {
      
          /**
           * Binds passed parameters according to their PHP type and executes
           * the prepared statement
           */
          function execute($params = array()) {
              $i = 1;
              foreach($params as $k => $v):
                  $mode = PDO::PARAM_STR;
                  if(is_null($v)) $mode = PDO::PARAM_NULL;
                  elseif(is_bool($v)) $mode = PDO::PARAM_BOOL;
                  elseif(is_resource($v)) $mode = PDO::PARAM_LOB;
                  $this->bindParam($i, $params[$k], $mode);
                  $i++;
              endforeach;
              $ok = parent::execute();
              return $ok ? $this : false;
          }
      
          /**
           * Returns the value of the first column of the first row
           */
          function getOne() {
              return $this->fetchColumn(0);
          }
      
          /**
           * Returns an array of values of the column found at $index
           * position.
           * @param $index
           */
          function getCol($index=0) {
              return $this->fetchAll(PDO::FETCH_COLUMN, $index);
          }
      
          /**
           * Returns all rows in numeric array format
           */
          function getArray(){
              return $this->fetchAll(PDO::FETCH_NUM);
          }
      
          /*
           * Returns all rows in associative array format
           */
          function getAll(){
              return $this->fetchAll(PDO::FETCH_ASSOC);
          }
      
          /**
           * Returns all rows in associative array format
           */
          function getAssoc() {
              return $this->fetchAll(PDO::FETCH_ASSOC);
          }
      
          /**
           * Returns rows in multi-dimensional format where the first
           * column is the key name and all other colums are grouped
           * into associative arrays for each row
           */
          function getGroup() {
              return $this->fetchAll(PDO::FETCH_GROUP);
          }
      
          /**
           * Returns a single row in associative format
           */
          function getRow(){
              return $this->fetch(PDO::FETCH_ASSOC);
          }
      
          /**
           * Fetches rows in array format where the first column
           * is the key name and all other columns are values
           */
          function getKeyPair(){
              //Emulate it
              $tmp = $this->fetchAll(PDO::FETCH_ASSOC);
              $arr = array();
              for($i = 0; $i < count($tmp); $i++){
                  $arr[array_shift($tmp[$i])] = count($tmp[$i]) > 1 ? $tmp[$i] : array_shift($tmp[$i]);
              }
              return $arr;
          }
      
          /**
           * Returns the number of rows returned by this statement
           */
          function recordCount(){
      
              return $this->rowCount();
      
          }
      
          /**
           * Returns the number of rows returned by this statement
           */
          function count(){
      
              return $this->rowCount();
      
          }
      }
      

      【讨论】:

      【解决方案8】:

      即使我的 DB 类不使用准备好的语句,我仍然想在这里提及它。我认为完全没有理由使用准备好的语句来实现所有内容。我确实知道准备好的语句更快,但只有在多次使用时才能使用。如果您只执行一次查询(这是我通常需要使用的唯一查询类型),它会较慢。因此,到处使用准备好的语句会适得其反。

      the class 的正确描述可以在some place else at stackoverflow 中找到。但这里有一些好东西:

      • 不到 100 行的数据库层
      • DB::x 代表 DB::instance()-&gt;executeDB::q 代表 DB::instance()-&gt;query
      • 使用两种类型的占位符??x 自动引用(其中x 可能是,&amp;|)。 ?, 占位符可在此处用作 UPDATE 助手。

      但有关完整信息,请参阅上面链接的 stackoverflow 帖子;)

      PS:repo 中的自述文件适用于这个类。它适用于普通的DB.php,而不适用于DB_intelligent.php。 PPS:该类是为 PHP 5.3 编写的。如果你想在 PHP 5.2 上使用它,只需将所有这些 PDO 方法从 DB_forPHP52.php 复制到 DB_intelligent.php 并删除 __callStatic 方法。

      【讨论】:

      • 是的,我对准备好的语句也有同样的看法,而且我对自己的数据库类/助手也很满意。但决定尝试 PDO。感谢您的课程,这是非常有趣的方法,我将对其进行彻底调查。这是我下一步要思考的好食物。我要记住的唯一一件事是使这些类/助手尽可能简单,以便可以在此处将其用作答案。我想到了完整的答案,与通常的“使用准备好的陈述”相反。给出一个可靠的工具作为答案,而不是草图。但可以理解。
      【解决方案9】:

      除了其他答案:正确引用列名的方法:

      /**
       * Escape identifier (database/table/column name) - ie. if you're using MySQL:
       * db_name.tbl_name.col_name -> `db_name`.`tbl_name`.`col_name`
       **/
      protected function quoteIdentifier($identifier) {
          static $escapeChars = array(
              'mysql'  => '``',
              'oracle' => '""',
              'mssql'  => '[]',
              //...
          );
      
          $escape = $escapeChars[$this->getAttribute(self::ATTR_DRIVER_NAME)];
          $identifier = (array) explode('.', $identifier);
          $identifier = array_map(function($segment) use($escape) {
              return $escape[0] . $segment . $escape[1];
          }, $identifier);
      
          return implode('.', $identifier);
      }
      

      【讨论】:

      • 这看起来很有趣。但我认为这并不完全正确。例如,如果 MySQL 在 ANSI 模式下运行,则使用 " 作为字段名称(虽然我不知道是否也支持 `)。我认为 PDO 本身不支持转义字段名称,这很遗憾。
      • 似乎 MySQL 在ANSI_QUOTES 模式下仍然允许`。在 PDO 中已经有一个 feature request 来实现这个 - 但它已经有四年历史了......
      • 谢谢,好点子。虽然我不打算实现任何数据库兼容性。这是一段很长的路要走,我怀疑我永远需要它。
      【解决方案10】:

      感谢大家。
      每个答案都很有帮助,我希望我能分红。

      最后,令我惊讶的是,根据接受的答案,我能够像以前一样做到这一点

      $fields = array("login","password");
      $_POST['password'] = MD5($_POST['login'].$_POST['password']);
      $stmt = $dbh->prepare("UPDATE users SET ".pdoSet($fields,$values)." WHERE id = :id");
      $values["id"] = $_POST['id'];
      $stmt->execute($values);
      

      它可以被包装成一个辅助函数,但我怀疑有没有必要。它只会将代码缩短一行。

      pdoSet 代码:

      function pdoSet($fields, &$values, $source = array()) {
        $set = '';
        $values = array();
        if (!$source) $source = &$_POST;
        foreach ($fields as $field) {
          if (isset($source[$field])) {
            $set.="`$field`=:$field, ";
            $values[$field] = $source[$field];
          }
        }
        return substr($set, 0, -2); 
      }
      

      【讨论】:

        【解决方案11】:

        插入查询通常需要许多占位符。问号样式因此难以阅读,命名参数是重复的并且容易出现输入错误。所以,我为整个插入查询创建了一个函数:

        function insert($table, $col_val){
            global $db;
            $table = preg_replace('/[^\da-z_]/i', '', $table);
            $smt = $db->prepare("DESCRIBE `$table`");
            $smt->execute();
            $columns = $smt->fetchAll(PDO::FETCH_COLUMN);
            $sets = array();
            $exec = array();
            foreach($col_val as $col => $val){
                if(!in_array($col, $columns))
                    return false;
                $sets[] .= "`$col`=?";
                $exec[] = $val;
            }
            $set = implode(',', $sets);
            $smt = $db->prepare("INSERT INTO `$table` SET $set");
            $smt->execute($exec);
            return $db->lastInsertId();
        }
        

        用法很简单:

        insert('table_name', array(
            'msg'   =>  'New message',
            'added' =>  date('Y-m-d H:i:s'),
        ));
        

        如果你需要lastInsertId():

        $new_id = insert(...
        

        【讨论】:

        • 这种方式容易发生SQL注入。请看The most fatal PDO code
        • @YourCommonSense 密钥将始终根据代码在服务器端确定。 insert('user', $_POST);$smt-&gt;execute($_POST); 一样容易被注入
        • 你错了。 $smt-&gt;execute($_POST); 不易发生。
        • @YourCommonSense 我的意思是容易被操纵。重点仍然存在:如果您允许用户创建密钥,那么他们可以做一些疯狂的事情,例如 $_POST['admin'] = 1
        • 这就是为什么你的函数应该过滤掉字段,而它没有。
        猜你喜欢
        • 2011-04-13
        • 2015-07-28
        • 2012-08-30
        • 2022-01-08
        • 2014-10-24
        • 2023-03-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多