使用PDO插入/更新助手函数。

时间:2022-10-13 12:12:19

I have a very simple helper function to produce SET statement for traditional plain mysql driver usage:

我有一个非常简单的助手函数来为传统的普通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); 
}

used like this

使用这样的

$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";

it makes code quite DRY and easy but flexible at the same time.

它使代码非常简单,同时又很灵活。

I gotta ask if anyone willing to share a similar function, utilizing PDO prepared statements feature?

我想问,是否有人愿意使用PDO准备语句特性来共享类似的功能?

I am still in doubts, how to accomplish this.
Is there a straight and simple way to use PDO prepared statements to insert data? What form it should be? Query builder helper? Or insert query helper? What parameters it should take?

我还在怀疑,如何才能做到这一点。是否有一种简单直接的方法使用PDO准备语句插入数据?它应该是什么形式?查询构建器辅助吗?或插入查询助手吗?需要什么参数?

I hope it can be easy enough to be used as an answer here on SO. Because in the every topic we can see prepared statements usage recommendation, but there is not a single good example. Real life example, I mean. To type bind_param() 20 times is not a good programming style I believe. And even 20 question marks too.

我希望它能足够简单地用作这里的答案。因为在每个主题中,我们可以看到准备好的语句使用推荐,但是没有一个好的例子。我是说现实生活中的例子。我认为输入bind_param() 20次不是一种好的编程风格。甚至还有20个问号。

11 个解决方案

#1


10  

I usually have a class extending PDO, but my class is pretty custom. If I get it cleaned up and tested I will post it at a later time. Here is a solution to your system, however.

我通常有一个扩展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);  

This may not be perfect and could use tweaking. It takes into account that $dbh is setup with a PDO Connection. Pending any minor syntax issues I made, that should work.

这可能并不完美,可能需要调整。它考虑到$dbh是使用PDO连接设置的。挂起我所做的任何小语法问题,这应该是可行的。

EDIT

编辑

Really though, I think I would go for Doctrine ORM (or another ORM). As you setup the model and add all the validation there, then it is as simple as:

不过,我想我还是会坚持教条主义(或者另一种)。当您建立模型并在其中添加所有验证时,它就简单到:

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

That should populate the contents easily. This is of course with an ORM, like Doctrine.

这样可以很容易地填充内容。这当然是一种兽性的教义。

UPDATED

更新

Did some minor tweaks to the first code, such as putting isset back and using rtrim over substr. Going to work on providing a mock up of a PDO Extension class just gotta layout the way to do it and do some unit tests to make sure it works.

对第一个代码做了一些细微的调整,例如将isset放回,并在substr上使用rtrim。要提供一个PDO扩展类的模型,只需要布局它的方式,并做一些单元测试来确保它能工作。

#2


5  

Thanks to everyone.
Every answer was helpful and I wish I could split the bounty.

多亏了每一个人。每一个回答都很有帮助,我希望我能平分这笔赏金。

At the end, to my surprise, I was able to make it the same way as before, based on on accepted answer

最后,令我惊讶的是,基于大家都接受的答案,我可以用和以前一样的方式写出来

$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);

It can be wrapped into a helper function, but I doubt there is necessity. It will shorten the code by just one line.

它可以封装到一个助手函数中,但是我怀疑是否有必要。它只会把代码缩短一行。

pdoSet code:

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); 
}

#3


4  

i would extend the Core PDO Class andd a method like so:

我将扩展核心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);
    }
}

Then use like so

然后使用一样

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

But as its already been said that you should be VERY weary of what your trying to do, you need to validate your data, its been sanitized but you not validated.

但是正如已经说过的,你应该对你的尝试感到厌倦,你需要验证你的数据,它被消毒了,但是你没有验证。

#4


2  

I've been patching something trivial together for what I consider recurring parameter binding cases. http://fossil.include-once.org/hybrid7/wiki/db

我将一些琐碎的东西拼凑在一起,用于考虑循环参数绑定的情况。http://fossil.include-once.org/hybrid7/wiki/db

Anyway; it provides some alternative prepared statement placeholders. Your example could be shortened into:

无论如何;它提供了一些备用的准备语句占位符。你的例子可以缩短为:

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

This case only works with named parameters, so $set would be array("name"=>..) and $where=array("id"=>123). The :, gets expanded on the first array you pass. It's replaced with comma-separated name=:name pairs (that's why its mnemonic is :,).

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

There are a few more placeholders :, :& :: and :? for different use cases. Only the ?? is really somewhat of a standard. So it needs some getting used to, but it significantly simplifies prepared statements and array binding (which PDO doesn't do natively).

还有一些占位符:,:&:和:?为不同的用例。仅? ?是一种标准。因此,它需要一些习惯,但它极大地简化了准备语句和数组绑定(PDO本身不这么做)。

#5


1  

Here's my general database abstraction class. Take a look at the autoExecute() function. It offers tons of flexibility for whatever it is you might want to accomplish. I should warn that this was written for PHP 5.3, and has been slightly tailored for PostgreSQL.

这是我的通用数据库抽象类。看一下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();

    }
}

#6


1  

Even though my DB class does not use prepared statements I still want to mention it here. I see no reason at all to implement everything with prepared statements. I do know that prepared statements are faster, but only when used multiple times. If you execute the query only once (and this is the only type of query I normally need to use), it is slower. Thus it is counterproductive to use prepared statements everywhere.

即使我的DB类不使用预准备语句,我仍然想在这里提一下。我认为完全没有理由用准备好的语句实现所有内容。我知道,准备好的语句比较快,但只有在多次使用时才知道。如果您只执行一次查询(这是我通常需要使用的唯一类型的查询),那么它会比较慢。因此,在任何地方使用预先准备好的语句都是适得其反的。

Proper description of the class may be found some place else at *. But here some of the good stuff:

在*中可以找到对该类的适当描述。但这里有一些好东西:

  • Less then 100 lines database layer
  • 少于100行数据库层
  • DB::x for DB::instance()->execute and DB::q for DB::instance()->query
  • DB:::x表示DB:::instance()->执行,DB::q表示DB:::instance()->查询
  • autoQuoting with two types of placeholders ? and ?x (where x may be ,, & and |). The ?, placeholder may be used as an UPDATE helper here.
  • 使用两种占位符进行自动引用?和?x (x可能在哪里,&和|)在这里,占位符可以用作更新助手。

But for full information see the * post linked above ;)

但要获得完整的信息,请参阅上面链接的*链接;

PS: The README in the repo does not apply to this class. It is for the normal DB.php, not for DB_intelligent.php. PPS: The class is written for PHP 5.3. If you want to use it on PHP 5.2 simply copy all those PDO methods from DB_forPHP52.php to DB_intelligent.php and remove the __callStatic method.

注:repo中的自述并不适用于这个类。这是正常的DB。php,不是DB_intelligent.php。pp:这个类是为PHP 5.3编写的。如果您想在PHP 5.2中使用它,只需从DB_forPHP52中复制所有这些PDO方法。php DB_intelligent。并删除__callStatic方法。

#7


1  

Just in addition to other answers: a method for proper quote of column names:

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

/**
 * 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);
}

#8


0  

You can extend PDO like that:

你可以这样扩展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));

#9


0  

Like others I've extended the standard PDO class to suit my needs. Something along the lines of this may suit you:

像其他人一样,我扩展了标准的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;
    }

}

Then you can use it quite simply:

然后你可以很简单地使用它:

// 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();
}

#10


0  

Insert queries often require many placeholders. The question mark style is then hard to read, and named parameters are repetitive and prone to typing errors. So, I created a function for the whole insert query:

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

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();
}

Usage is simple:

使用很简单:

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

And if you need lastInsertId():

如果需要lastInsertId():

$new_id = insert(...

#11


-1  

Reference: How can I prevent SQL injection in PHP?

如何防止PHP中的SQL注入?

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

#1


10  

I usually have a class extending PDO, but my class is pretty custom. If I get it cleaned up and tested I will post it at a later time. Here is a solution to your system, however.

我通常有一个扩展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);  

This may not be perfect and could use tweaking. It takes into account that $dbh is setup with a PDO Connection. Pending any minor syntax issues I made, that should work.

这可能并不完美,可能需要调整。它考虑到$dbh是使用PDO连接设置的。挂起我所做的任何小语法问题,这应该是可行的。

EDIT

编辑

Really though, I think I would go for Doctrine ORM (or another ORM). As you setup the model and add all the validation there, then it is as simple as:

不过,我想我还是会坚持教条主义(或者另一种)。当您建立模型并在其中添加所有验证时,它就简单到:

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

That should populate the contents easily. This is of course with an ORM, like Doctrine.

这样可以很容易地填充内容。这当然是一种兽性的教义。

UPDATED

更新

Did some minor tweaks to the first code, such as putting isset back and using rtrim over substr. Going to work on providing a mock up of a PDO Extension class just gotta layout the way to do it and do some unit tests to make sure it works.

对第一个代码做了一些细微的调整,例如将isset放回,并在substr上使用rtrim。要提供一个PDO扩展类的模型,只需要布局它的方式,并做一些单元测试来确保它能工作。

#2


5  

Thanks to everyone.
Every answer was helpful and I wish I could split the bounty.

多亏了每一个人。每一个回答都很有帮助,我希望我能平分这笔赏金。

At the end, to my surprise, I was able to make it the same way as before, based on on accepted answer

最后,令我惊讶的是,基于大家都接受的答案,我可以用和以前一样的方式写出来

$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);

It can be wrapped into a helper function, but I doubt there is necessity. It will shorten the code by just one line.

它可以封装到一个助手函数中,但是我怀疑是否有必要。它只会把代码缩短一行。

pdoSet code:

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); 
}

#3


4  

i would extend the Core PDO Class andd a method like so:

我将扩展核心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);
    }
}

Then use like so

然后使用一样

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

But as its already been said that you should be VERY weary of what your trying to do, you need to validate your data, its been sanitized but you not validated.

但是正如已经说过的,你应该对你的尝试感到厌倦,你需要验证你的数据,它被消毒了,但是你没有验证。

#4


2  

I've been patching something trivial together for what I consider recurring parameter binding cases. http://fossil.include-once.org/hybrid7/wiki/db

我将一些琐碎的东西拼凑在一起,用于考虑循环参数绑定的情况。http://fossil.include-once.org/hybrid7/wiki/db

Anyway; it provides some alternative prepared statement placeholders. Your example could be shortened into:

无论如何;它提供了一些备用的准备语句占位符。你的例子可以缩短为:

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

This case only works with named parameters, so $set would be array("name"=>..) and $where=array("id"=>123). The :, gets expanded on the first array you pass. It's replaced with comma-separated name=:name pairs (that's why its mnemonic is :,).

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

There are a few more placeholders :, :& :: and :? for different use cases. Only the ?? is really somewhat of a standard. So it needs some getting used to, but it significantly simplifies prepared statements and array binding (which PDO doesn't do natively).

还有一些占位符:,:&:和:?为不同的用例。仅? ?是一种标准。因此,它需要一些习惯,但它极大地简化了准备语句和数组绑定(PDO本身不这么做)。

#5


1  

Here's my general database abstraction class. Take a look at the autoExecute() function. It offers tons of flexibility for whatever it is you might want to accomplish. I should warn that this was written for PHP 5.3, and has been slightly tailored for PostgreSQL.

这是我的通用数据库抽象类。看一下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();

    }
}

#6


1  

Even though my DB class does not use prepared statements I still want to mention it here. I see no reason at all to implement everything with prepared statements. I do know that prepared statements are faster, but only when used multiple times. If you execute the query only once (and this is the only type of query I normally need to use), it is slower. Thus it is counterproductive to use prepared statements everywhere.

即使我的DB类不使用预准备语句,我仍然想在这里提一下。我认为完全没有理由用准备好的语句实现所有内容。我知道,准备好的语句比较快,但只有在多次使用时才知道。如果您只执行一次查询(这是我通常需要使用的唯一类型的查询),那么它会比较慢。因此,在任何地方使用预先准备好的语句都是适得其反的。

Proper description of the class may be found some place else at *. But here some of the good stuff:

在*中可以找到对该类的适当描述。但这里有一些好东西:

  • Less then 100 lines database layer
  • 少于100行数据库层
  • DB::x for DB::instance()->execute and DB::q for DB::instance()->query
  • DB:::x表示DB:::instance()->执行,DB::q表示DB:::instance()->查询
  • autoQuoting with two types of placeholders ? and ?x (where x may be ,, & and |). The ?, placeholder may be used as an UPDATE helper here.
  • 使用两种占位符进行自动引用?和?x (x可能在哪里,&和|)在这里,占位符可以用作更新助手。

But for full information see the * post linked above ;)

但要获得完整的信息,请参阅上面链接的*链接;

PS: The README in the repo does not apply to this class. It is for the normal DB.php, not for DB_intelligent.php. PPS: The class is written for PHP 5.3. If you want to use it on PHP 5.2 simply copy all those PDO methods from DB_forPHP52.php to DB_intelligent.php and remove the __callStatic method.

注:repo中的自述并不适用于这个类。这是正常的DB。php,不是DB_intelligent.php。pp:这个类是为PHP 5.3编写的。如果您想在PHP 5.2中使用它,只需从DB_forPHP52中复制所有这些PDO方法。php DB_intelligent。并删除__callStatic方法。

#7


1  

Just in addition to other answers: a method for proper quote of column names:

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

/**
 * 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);
}

#8


0  

You can extend PDO like that:

你可以这样扩展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));

#9


0  

Like others I've extended the standard PDO class to suit my needs. Something along the lines of this may suit you:

像其他人一样,我扩展了标准的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;
    }

}

Then you can use it quite simply:

然后你可以很简单地使用它:

// 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();
}

#10


0  

Insert queries often require many placeholders. The question mark style is then hard to read, and named parameters are repetitive and prone to typing errors. So, I created a function for the whole insert query:

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

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();
}

Usage is simple:

使用很简单:

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

And if you need lastInsertId():

如果需要lastInsertId():

$new_id = insert(...

#11


-1  

Reference: How can I prevent SQL injection in PHP?

如何防止PHP中的SQL注入?

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