2010-09-22 17 views
13

tengo una función de ayuda muy simple para producir instrucción SET para uso tradicional controlador llanura mysql:función Insertar/actualizar ayudante usando DOP

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

utilizado como esto

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

que hace que el código bastante seco y fácil pero flexible al mismo tiempo.

Tengo que preguntar si alguien dispuesto a compartir una función similar, utilizando PDO preparó cuentan con declaraciones?

Todavía estoy en dudas, la forma de lograr esto.
¿Existe una manera simple y directa de usar declaraciones preparadas con PDO para insertar datos? ¿Qué forma debería ser? Query Builder Helper? ¿O insertar ayudante de consulta? ¿Qué parámetros debería tomar?

espero que pueda ser lo suficientemente fácil para ser utilizado como una respuesta aquí en la SO. Porque en cada tema podemos ver recomendaciones de uso de declaraciones preparadas, pero no hay un solo buen ejemplo. Ejemplo de la vida real, quiero decir. Escribir bind_param() 20 veces no es un buen estilo de programación, creo. Y hasta 20 signos de interrogación también.

+0

@Col: ¿Qué es exactamente lo que busca ¿para? ¿Solo algo para las actualizaciones? O todo tipo de marcadores de posición? [My DB wrapper] (http://github.com/nikic/DB) admite marcadores de posición '? I,? S,? A' para enteros, cadenas y matrices. No sé si esto será suficiente para ti. – NikiC

+0

¿Está dispuesto a dar diferentes clases de abstracción de base de datos/sistemas ORM en lugar de sus funciones php estándar habituales. – RobertPitt

+0

¿Realmente sigues usando la lib mysql, ni siquiera mysqli? ¿POR QUÉ? –

Respuesta

10

por lo general tienen una clase que se extiende DOP, pero mi clase es bastante personalizado. Si lo limpio y lo pruebo, lo publicaré más adelante. Aquí hay una solución para su sistema, sin embargo.

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

Esto puede no ser perfecto y podría usar ajustes. Tiene en cuenta que $dbh está configurado con una conexión PDO. A la espera de cualquier problema de sintaxis menor que hice, eso debería funcionar.

EDITAR

Realmente sin embargo, creo que me gustaría ir para ORM de Doctrine (u otro ORM). A medida que el modelo de configuración y añadir toda la validación de ahí, entonces es tan simple como:

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

Eso se debe rellenar el contenido con facilidad. Esto es, por supuesto, con un ORM, como Doctrine.

ACTUALIZADO

hecho un poco de ajustes menores en el primer código, como poner isset atrás y utilizando rtrim sobre substr. Ir a trabajar en el suministro de una maqueta de una clase de Extensión PDO solo tiene que diseñar el camino para hacerlo y hacer algunas pruebas de unidad para asegurarse de que funcione.

+0

Todavía estoy tratando de captar PDO, pero ¿no es necesario vincular las variables? ¿Alguien me puede explicar esto? – Catfish

+0

gracias, tenía algo como esto en mi mente. sí, tenemos que preparar $ values ​​array también. –

+0

@catfish, el establecimiento de una matriz de valores a utilizar en la función 'execute' alivia la necesidad de vincular los parámetros, si se utiliza el' ', no puedo hablar con el':? Variable' ya que nunca he utilizado ese método. El orden de la matriz sí importa, así que ten cuidado con eso. –

-1

Referencia: 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(); 
+0

Creo que estaba buscando una forma de conjunto de modificar su código actual para trabajar con DOP, no una respuesta "cookie cutter" a la manera de usa PDO. –

+0

¿por qué redefinir la rueda? tres líneas es todo lo que toma –

+0

Puedo encontrar ejemplos ficticios yo mismo. Pero estos ejemplos no son para la vida real. Escribir el nombre de cada campo ** tres ** veces no es un trabajo con el que sueñe. –

4

i extendería la DOP Clase Core andá un método de este modo:

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

A continuación, utilice como tal

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

Pero como ya se ha dijo que deberías estar MUY cansado de lo que intentas hacer, necesitas valide sus datos, ha sido desinfectado pero no validado.

+0

Bueno, es consulta SELECT, pero yo más bien interesado en la automatización consulta INSERT.Estoy validando la entrada usando una matriz con nombres de campo permitidos. Parece que es el único método confiable. –

+0

¿no estás usando un frameowrk o un patrón en particular? – RobertPitt

+0

Estoy abierto a utilizar algún marco, si me parece sensato y utilizable. –

2

He estado parcheando algo trivial juntos por lo que considero recurrentes casos de enlace de parámetros. http://fossil.include-once.org/hybrid7/wiki/db

Anyway; proporciona algunos alternative prepared statement placeholders. Su ejemplo podría ser acortado en:

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

Este caso sólo funciona con parámetros con nombre, por lo que sería $ conjunto array ("nombre" => ..) y donde $ = array ("id" => 123) . El :, se expande en la primera matriz que pasa. Se reemplaza con coma -separado name =: name pairs (es por eso que su mnemónico es :,).

Hay unos cuantos más marcadores de posición :,:&:: y :? para diferentes casos de uso. Solo el ?? es realmente un poco estándar. Por lo tanto, es necesario acostumbrarse, pero simplifica significativamente las declaraciones preparadas y el enlace de matriz (que PDO no hace de forma nativa).

+0

Oh genial. Le daré un vistazo. Estaba pensando en algo de este tipo, porque el conjunto estándar de marcadores de posición es pobre. Imagine que necesita hacer una declaración IN de algún arreglo. Demasiado trabajo manual que se puede automatizar fácilmente mediante el uso de marcador de posición especial. –

+0

Exactamente. Este fue el caso de uso original que me llevó a ello. He sido una característica que lo arrastra desde entonces. '??' es el más versátil, es por eso que Perls DBIx también lo usa. Y el parche de cadena simplemente parece ser la segunda mejor alternativa al soporte de lenguaje LINQ. – mario

+0

gran concepto y buena implementación. Lo analizaré más de cerca la próxima semana. Personalmente, solo tengo un ayudante que crea marcadores de posición para una consulta como @premiso hace arriba, pero esto es más elegante y hace un sql en código un poco más legible. – Fanis

0

Puede extender DOP así:

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

Al igual que otros que he ampliado la clase estándar de DOP para satisfacer mis necesidades. Algo a lo largo de las líneas de esto puede adaptarse a usted:

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

} 

A continuación, puede utilizarlo simplemente:

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

Aquí es mi clase general de abstracción de base de datos. Eche un vistazo a la función autoExecute(). Ofrece toneladas de flexibilidad para lo que sea que desee lograr. Debo advertir que esto fue escrito para PHP 5.3, y ha sido ligeramente adaptado para 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 <[email protected]> 
* @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(); 

    } 
} 
+0

Ahora hay un repositorio github que contiene esta clase, con muchas otras bibliotecas útiles. Compruébelo en https://github.com/kenaniah/insight – Kenaniah

1

Aunque mi clase de base de datos no utiliza sentencias preparadas, todavía quiero mencionarla aquí. No veo ninguna razón para implementar todo con declaraciones preparadas. Sé que las declaraciones preparadas son más rápido, pero solo cuando se usa varias veces. Si ejecuta la consulta solo una vez (y este es el único tipo de consulta que normalmente necesito usar), es más lento. Por lo tanto, es contraproducente usar declaraciones preparadas en todas partes.

Descripción adecuada de the class se puede encontrar some place else at stackoverflow. Pero aquí algunas de las cosas buenas:

  • Menos de 100 líneas capa de base de datos
  • DB::x para DB::instance()->execute y DB::q para DB::instance()->query
  • autoQuoting con dos tipos de marcadores de posición ? y ?x (donde x puede haber ,, & y |). El marcador de posición ?, se puede usar como ayudante de ACTUALIZACIÓN aquí.

Pero para obtener información completa ver el mensaje stackoverflow vinculado anteriormente;)

PS: El README en el repositorio hace no se aplican a esta clase. Es para el DB.php normal, no para el DB_intelligent.php. PPS: la clase está escrita para PHP 5.3. Si desea usarlo en PHP 5.2 simplemente copie todos esos métodos PDO de DB_forPHP52.php a DB_intelligent.php y elimine el método __callStatic.

+0

Sí, pienso lo mismo sobre las declaraciones preparadas, y estoy contento con mi propia clase de base de datos/ayudantes también. Pero decidió probar PDO. Gracias por su clase, es un enfoque muy interesante, voy a investigarlo detenidamente. Es un buen alimento para los pensamientos para el siguiente paso que voy a tomar. Lo único que trato de tener en cuenta es mantener estas clases/ayudantes lo más fácil posible, para que sea posible usarlo como respuestas aquí en SO. Pienso en respuestas completas, opuestas a las usuales declaraciones de "uso preparado". Para dar una herramienta confiable como respuesta, no un boceto. Pero comprensible. –

1

Así, además de otras respuestas: un método para la cita correcta de nombres de columna:

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

Esto parece interesante. Pero creo que esto no es totalmente correcto. Por ejemplo, si MySQL se ejecuta en modo ANSI, utiliza '' 'para nombres de campo (aunque no sé si' \ '' también es compatible). Creo que es muy triste que PDO no admita el escape de nombres de campo de forma nativa. – NikiC

+0

Parece que MySQL aún permite el modo '\' 'en' ANSI_QUOTES'. Ya hay una [solicitud de función] (http://bugs.php.net/bug.php?id=38196) para implementar esto en PDO - pero tiene cuatro años ... – NikiC

+0

Gracias, buen punto. Aunque no voy a implementar ninguna compatibilidad con la base de datos. Es un largo camino por recorrer y dudo que alguna vez lo necesite. –

5

Gracias a todos.
Cada respuesta fue útil y desearía poder dividir la recompensa.

Al final, para mi sorpresa, yo era capaz de hacer que la misma forma que antes, basado en el respuesta aceptada

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

Puede ser envuelto en una función auxiliar, pero dudo que hay necesidad. Acortará el código con solo una línea.

código 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); 
} 
+0

+1 para el post! – abel

0

consultas de inserción a menudo requieren muchos marcadores de posición. El estilo de signo de interrogación es difícil de leer y los parámetros nombrados son repetitivos y propensos a errores de tipeo. Por lo tanto, he creado una función para toda la consulta de inserción:

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

uso es simple:

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

Y si necesita lastInsertId():

$new_id = insert(... 
+0

Este enfoque es propenso a la inyección SQL. Consulte [El código PDO más fatal] (https://phpdelusions.net/pdo/lame_update) –

+0

@YourCommonSense Las claves siempre se determinarán en el lado del servidor según el código. 'insert ('user) ', $ _POST); 'es tan propenso a la inyección como' $ smt-> execute ($ _ POST); ' – rybo111

+0

Estás equivocado aquí. '$ smt-> execute ($ _ POST);' NO es propenso. –

Cuestiones relacionadas