martes, 16 de julio de 2013

PDO - Parte 2

Otras funciones útiles de PDO 

Existen tres funciones importantes para obtener información de la consulta ejecutada sin tener que recorrer los resultados:

A) Obtener el id de la última fila o secuencia insertada en la base de datos.

 public string PDO::lastInsertId ([ string $name = NULL ] )  

El parámetro $name opcional, sirve para indicar el nombre de columna. Como PDO depende de los drivers para cada gestor de base de datos soportado, en alguno de estos quizás sea necesario especificar el nombre de columna que almacena los identificadores. Como ocurre en PostgreSQL.

 $conn = new PDO('mysql:dbname=test;host=127.0.0.1', 'user', 'password');  
 $stmt = $conn->prepare('INSERT INTO test (name) VALUES (:name)');  
 $stmt->execute([':name' => 'foo']);  
 var_dump($conn->lastInsertId());  

Recuerda que la función es de la clase PDO y no de PDOStatement.

B) Obtener el numero de filas afectadas por la ultima sentencia de inserción, borrado o actualización. Función de la clase PDOStatement.

 public int PDOStatement::rowCount ( void )  

 $stmt = $con->prepare('DELETE FROM usuario');  
 $stmt->execute();  
 $count = $stmt->rowCount();  
 print("se borraron: $count filas.\n");  

Esta función es muy útil en el caso de que queremos borrar una fila, por ejemplo un único usuario. De esta manera comprobaríamos cuantas filas se han borrado y si el resultado es cero es que ha fallado y si es 1 es que ha funcionado.

Como PDO depende de los drivers de las bases de datos que soporta, PDO no asegura que para todas las bases de datos, a función rowCount() funcione correctamente para sentencias de tipo SELECT. Sin embargo tampoco es absolutamente necesaria para consultas de selección, ya que podemos utilizar otras soluciones:
- Para saber el número total de filas siempre es mejor averiguarlo usando la base de datos. Y después de ejecutar la consulta utilizar la función fetchColumn([$num_columna = 0]), que devolverá el resultado de la columna que se le indique como parámetro opcional (cero por defecto) en la primera fila de resultados.

 $sql = "SELECT COUNT(*) FROM fruit WHERE calories > 100";  
 $res = $conn->query($sql){  
 if ($res->fetchColumn() > 0) {  
 }  

- Si lo que queremos es utilizamos un bucle para obtener cada fila y trabajar con ellas, el bucle terminará cuando no haya más filas. Si desde un principio no hay filas. el bucle no ejecutará nada.
- Si la comprobación va destinada a ver si existe un usuario. Simplemente obtenemos la única fila que deberíamos obtener con fetch(). Y como la función fetch() devuelve false si ha podido obtener ninguna fila. Ya sabríamos si existe o no el usuario. Por lo que tampoco necesitamos usar rowCount().

 public function getUsuario($usuario, $password){  
     $datos = $this->_db->query(  
         "select * from usuarios " .  
         "where usuario = '$usuario' " .  
         "and pass = '" . Hash::getHash('sha1',$password,HASH_KEY) ."'"  
         );  
     //fetch devuelve false en caso de consulta sin resultados  
     return $datos->fetch();  
  }  
 ...  
 $row = $this->getUsuario($usuario, $pass);  
 if (!$row) {  
 ...  
 }  

Estas funciones tienen su equivalente en MySQLi en consultas normales o para consultas preparadas.

Ejemplos

Inserción

 try {   
   $dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);   
   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);   
   $stmt = $db->prepare("INSERT INTO table(field1,field2,field3) VALUES(:field1,:field2,:field3)");  
   $stmt->execute(array(':field1' => $field1, ':field2' => $field2, ':field3' => $field3));  
   $affected_rows = $stmt->rowCount();  
 }   
 catch(PDOException $e)  {   
   echo $sql . '<br />' . $e->getMessage();   
 }   

Inserción múltiple

 try {   
   $dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);   
   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);   
   $values = array('bob', 'alice', 'lisa', 'john');  
   $name = '';  
   $stmt = $db->prepare("INSERT INTO table(`name`) VALUES(:name)");  
   $stmt->bindParam(':name', $name, PDO::PARAM_STR);  
   foreach($values as $name) {  
     $stmt->execute();  
   }  
 }   
 catch(PDOException $e)  {   
   echo $sql . '<br />' . $e->getMessage();   
 }   

Borrado

 try {   
   $dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);   
   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);   
   $stmt = $db->prepare("DELETE FROM table WHERE id=:id");  
   $stmt->bindValue(':id', $id, PDO::PARAM_INT);  
   $stmt->execute();  
   $affected_rows = $stmt->rowCount();  
 }   
 catch(PDOException $e)  {   
   echo $sql . '<br />' . $e->getMessage();   
 }   

Actualización

 try {   
   $dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);   
   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);   
   $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");  
   $stmt->execute(array($name, $id));  
   $affected_rows = $stmt->rowCount();  
 }   
 catch(PDOException $e)  {   
   echo $sql . '<br />' . $e->getMessage();   
 }   

Recuerda que si las en funciones execute(), bindValue() o bindParam no especificamos el tipo del dato se tratará por defecto como PARAM_STR. Por lo tanto se filtrarán como cadenas.

Transacciones

Ya explicamos que son y como funcionan las transacciones con la extensión MySqli Y con PDO funcionan de una manera muy similar. En resumen, son un mecanismo que nos asegura que todas y cada de las consultas que las forman van a ser ejecutadas, de forma segura y sin interferencia de otras conexiones. En el caso de que falle alguna consulta, la transacción no se llevará a cabo.
Pero recuerda que PDO trabaja como capa de abstracción sobre múltiples tipos de gestores de bases de datos así que cada uno de estos gestores tienen sus reglas y es posible que alguno no admita el mecanismo de transacciones. Con MySQL, por ejemplo, hay que tener cuidado con el tipo de motores de almacenamiento de las tablas. Ya que todos no soportan las transacciones. Los tipos de tablas MyISAM no permiten transacciones. Así que transacciones solo se pueden llevar a cabo sobre tablas InnoDB.

Vamos a ver el proceso de crear transacciones:

1) Iniciar la transacciones.

PDO, al igual que pasaba con MySQLi, ejecuta por defecto el modo "autocommit". Lo que significa que cada consulta ejecutada se trata como una transacción implícitamente.  Por lo tanto el primer paso para usar las transacciones es desactivar este modo. Y así poder decidir donde empieza y donde acaba nuestra transacción. Con lo que la transacción no se llevará a cabo hasta que se lo indiquemos.
Para ello utilizaremos la siguiente función:

 public bool PDO::beginTransaction ( void )  

Que devolverá True en caso de acierto y False en caso de error. Además, Si el controlador subyacente no admite transacciones, se lanzará una PDOException (independientemente de la configuración del manejo de errores: esto es siempre una condición de error serio).

2) Crear consultas y  finalizar o revertir transacción

Una vez se han creado las consultas podemos finalizar la transacción si no ha habido ningún error. Para ello nos podemos ayudar de las excepciones. O revertir la transacción, en caso de algún error.
Antes de mostrar un ejemplo completo vamos a presentar una función de la clase PDO que no habíamos mostrado hasta ahora.

 public int PDO::exec ( string $statement )  

Dicha función ejecuta una sentencia sql y devuelve el numero de filas afectadas por dicha ejecución. Podría considerarse una alternativa similar a query(). Sin embargo no devuelve  los resultados de una sentencia SELECT. Por lo tanto su funcionalidad esta limita a consultas donde no vayamos a obtener un conjunto de resultados (selecciones).  Donde solo nos importe que se ejecuten correctamente. Pero para ejecutar transacciones, donde lo normal no ejecutar consultas SELECT, es ideal.

Función para ejecutar la transacción. Hasta que no se indique ninguna de las consultas de la transacción se habrán ejecutado.

 public bool PDO::commit ( void )  

Función para revertir  la transacción. Con dicha función cancelaremos la transacción.

 public bool PDO::rollBack ( void )  

Hay que tener en cuenta que tanto si se confirma una transacción o se revierte, devolveremos la conexión de base de datos a modalidad "autocommit" hasta que la siguiente llamada a PDO::beginTransaction() inicia una nueva transacción.

 try {  
   $dbh = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password);  
   echo 'Conectado a la base de datos<br />';  
   /*** Poner el modo de errores para detectar excepciones ***/  
   $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
   /*** Empezamos la transacción ***/  
   $dbh->beginTransaction();  
   /*** Creamos tabla ***/  
   $table = "CREATE TABLE animals ( animal_id MEDIUMINT(8) NOT NULL AUTO_INCREMENT PRIMARY KEY,  
   animal_type VARCHAR(25) NOT NULL,  
   animal_name VARCHAR(25) NOT NULL   
   )";  
   $dbh->exec($table);  
   /*** insertamos datos ***/  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('emu', 'johnny')");  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('funnel web', 'bruce')");  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('lizard', 'liz')");  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('dingo', 'dango')");  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('kangaroo', 'bob')");  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('wallaby', 'wally')");  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('wombat', 'rick')");  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('koala', 'mowgli')");  
   $dbh->exec("INSERT INTO animals (animal_type, animal_name) VALUES ('kiwi', 'tonny')");  
   /*** Enviamos la transacción ***/  
   $dbh->commit();  
   echo 'Datos insertados correctamente<br />';  
 }  
 catch(PDOException $e)  {  
   /*** revertimos transacción por agun fallo ***/  
   $dbh->rollback();  
   echo $sql . '<br />' . $e->getMessage();  
 }  


Entradas relacionadas

PDO - Parte 1

2 comentarios:

  1. Hola, buen artículo. Una consulta para que sirve crear transacciones?

    ResponderEliminar
  2. Excelente post Ivan, de nuevo te luciste con la explicación y los ejemplos

    ResponderEliminar