viernes 27 de abril de 2007

Accesando a nuestra base de datos con PDO

PHP y MySQL, se llevan de la mano, es muy sencillo y casi todos los paquetes "todo en uno" implementan PHP y MySQL, esto le dio a PHP una ventaja muy grande y un crecimiento muy grande.

El problema viene que muchos programadores inexpertos se pusieron a programar, y a hacer micro sistemas, o scripts, que en si sirven para su función, pero pueden llegar a ser inseguros y los hackers empiezan a buscar agujeros para explotar sus funciones y poder entrar a la base de datos y borrar, o cambiar todo.

Este es un problema grave (investiguen el caso de PHP-Nuke o PHPBB en sus primeras versiones), ya que programando webs inseguras, ocurren varias cosas, nos vemos mal como programadores, y damos mala fama a todos los programadores web.

Para evitar esto hay que ser extremadamente cuidadosos en las variables que enviamos para entrar a la base de datos, dichas variables SIEMPRE tienen que ser verificadas que son los datos que esperamos, en pocas palabras existe una regla de oro:

Nunca confíes en el contenido que te envía el usuario

¿Como podemos remediarlo? Puede ser un dolor de cabeza, pero en cada variable que envíes tienes que checar si esta activado o no magic_quotes (otro error en PHP que por defecto viene activado), luego escapar todos tus strings con addslashes y mysql_real_escape_string, luego checar tus querys y ver que no permitas ningún agujero en tu código.

Esto es un dolor de cabeza pero es necesario, ahora, que pasa si derrepente te cambian tu base de datos, que pasa si tu cliente en lugar de mysql, decide que quiere MSSQL o PostgreSQL. Tendrías ahora mas dolores de cabeza, para remediar esto, y para evitar programar mal desde la version 5.1 de PHP viene por defecto (y activado) PDO.

PDO por sus siglas en Ingles (PHP Data Objects) Objetos de Datos de PHP. PDO es un driver de acceso a varias bases de datos por lo que es ideal que cuando empieces a programar lo hagas sobre este driver.

Este pequeño mini tutorial te dirá como entrar, seleccionar, insertar y actualizar datos en tu base de datos favorita: MySQL, si requieres cambiar a otra base de datos, por lo general solo cambiaras una linea de tu código.

Como conectar a MySQL

Para iniciar una conexión a MySQL usamos el constructor PDO:

try {
$db = new PDO('mysql:dbname=testdb;host=localhost', $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Cerramos la conexion
$db = null;
}
catch( PDOException $e ) {
echo
"Error de conexion: " . $e->getMessage();
}



Facil, no utilizamos ni mysql_connect, ni mysql_close, el constructor PDO nos crea una instancia del driver sobre el que podemos trabajar.

Toda instancia de PDO si le pasamos como atributo que PDO::ERRMODE_EXCEPTION lanzara una excepcion en cuanto tenga un error, esto es muy util ya que nos concentraremos en realizar el trabajo, y si de paso estamos usando transacciones, si hay una excepcion automaticamente son regresadas garantizando que nuestros datos sean consistentes.

Ahora enviar querys, es sencillo, ya que automáticamente las variables son protegidas contra ataques de SQL Inyection. Aquí si cambia un poco la forma, ya que usamos lo que se le conoce como Prepared Statements o Comandos Preparados, es igual de sencillo, cuando preparamos un query (ya sea Insert, Select, Update, Call, etc.).

La forma es sencillo:

try {
$db = new PDO('mysql:dbname=testdb;host=localhost', $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Preparamos un Query
$comando = $db->prepare("SELECT * FROM Clientes WHERE Cliente LIKE ?");
// Obtenemos el comando desde GET
$cliente = '%' . $_GET['Cliente'] . '%';

$comando->execute( array( $cliente ) );

while(
$row = $comando->fetch() ) {
var_dump( $row ); // Imprimimos resultados
}

// Cerramos la conexion
$comando = null;
$db = null;
}
catch( PDOException $e ) {
echo
"Error de conexión: " . $e->getMessage();
}

Como podemos ver, podemos usar directamente el valor de nuestro $_GET sin temor a que sea una cadena que pueda perjudicarnos, ya que el driver automáticamente se encarga de analizar y enviar la cadena como un string.

Si vemos el query hay algo diferente y es el uso de lo que se le conoce como marcadores o "placeholders", esto es el signo de interrogación en el WHERE "?". Estos marcadores es lo que se va a reemplazar por el valor que tu le asignes cuando uses execute(). Para actualizar solo debes de cambiar tu Query:

UPDATE Clientes SET Nombre=? WHERE idCliente=?

Y enviar tu query:

$comando->execute( array( "Juan", 1 );

Como puedes ver, no es necesario escapar las variables, ni mucho menos, esto te da la facilidad, y te quita una preocupación.

Otra ventaja de usar PDO en lugar de las funciones de mysql normales, es que podemos extenderlas y crear nuestra propia clase de bases de datos, agregarle el patron Singleton y tener ya lista una clase para trabajar con nuestro sistema.

Un rapido ejemplo:
class DB extends PDO {
private static $_instance = null;

private function __construct() {
// Obtenemos el Config desde un array, clase config etc:
$config = Config::getInstance();
$db = $config->DBName;
$user = $config->UserName;
$pwd = $config->Password;

$attrs = array();
$dsn = 'mysql:host=localhost;dbname=' . $db;

parent::__construct( $dsn, $user, $pwd, $attrs );
$this->setAtribute( PDO::ATTR_ERRMODE, PDO::ERR_MODE_EXCEPTION );
}

public static function getInstance() {
if(
self::$_instance == null ) {
self::$_instance = new self();
}

return
self::$_instance;
}
}


Espero que con este tutorial te puedas dar una idea de las ventajas de usar PDO en lugar de las funciones de mysql normales, y de paso abrirte camino a la abstracción de datos.

Suerte.

2 comentarios:

Luis Miguel dijo...

Hola, que tal. Donde podria encontrar el codigo de la clase PDO.


Saludos!

Abel dijo...

Muy buen manual.. actualmente estoy trabajando un poco con PDO y tengo algunos problemillas al hacer un execute() o query().

Necesito que me regrese los resultados para manipularlos externamente, si puedes ayudarme te lo agradecería mucho!

Saludos