Volver al índicePHP Data Objects (PDO)

La extensión PDO (PHP Data Objects) permite acceder a distintas bases de datos utilizando las misma funciones, lo que facilita la portabilidad. En PHP 5 existen drivers para acceder a las bases de datos más populares (MySQL, Oracle, MS SQL Server, PostgreSQL, SQLite, Firebird, DB2, Informix, etc).

En esta lección se explica el acceso a MySQL y SQLite mediante PDO. La extensión PDO no evalúa la correción de las consultas SQL.


Conexión con la base de datos

Para conectar con la base de datos hay que crear una instancia de la clase PDO, que se utiliza en todas las consultas posteriores. En cada página php que incluya consultas a la base de datos es necesario conectar primero con la base de datos.

Si no se puede establecer la conexión con la base de datos, puede deberse a que la base de datos no esté funcionando, a que los datos de usuario no sean correctos, a que no esté activada la extensión pdo o (en el caso de SQLite) que no exista el camino donde se guarda la base de datos.

Conexión con MySQL

En el caso de MySQL, la creación de la clase PDO incluye el nombre del servidor, el nombre de usuario y la contraseña.

Para poder acceder a MySQL mediante PDO, debe estar activada la extensión php_pdo_mysql en el archivo de configuración php.ini (véase el apartado extensión pdo_mysql en la lección de configuración de Apache y PHP).

// FUNCIÓN DE CONEXIÓN CON LA BASE DE DATOS MYSQL
function conectaDb()
{
    try {
        $db = new PDO('mysql:host=localhost', 'root', '');
        $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, TRUE);
        return($db);
    } catch (PDOException $e) {
        cabecera('Error grave');
        print "<p>Error: No puede conectarse con la base de datos.</p>\n";
//      print "<p>Error: ".$e->getMessage()."</p>\n";
        pie();
        exit();
    }
}

// EJEMPLO DE USO DE LA FUNCIÓN ANTERIOR
// La conexión se debe realizar en cada página que acceda a la base de datos
$db = conectaDB();

Conexión con SQLite 3

En SQLite, no se hace una conexión a un servidor, sino que simplemente se indica el archivo que va a contener la base de datos. En SQLite no hay un servidor que gestiona todas las bases de datos, sino que cada base de datos es un archivo independiente (que debe estar situado en un directorio que exista y en el que el servidor web tenga permisos de escritura).

Para poder utilizar SQLite mediante PDO, debe estar activada la extensión php_pdo_sqlite en el archivo de configuración php.ini (véase el apartado extensión pdo_sqlite en la lección de configuración de Apache y PHP).

// FUNCIÓN DE CONEXIÓN CON LA BASE DE DATOS SQLITE
function conectaDb()
{
    try {
        $db = new PDO('sqlite:/tmp/mclibre_baseDeDatos.sqlite');
        return($db);
    } catch (PDOException $e) {
        cabecera('Error grave');
        print "<p>Error: No puede conectarse con la base de datos.</p>\n";
//      print "<p>Error: ".$e->getMessage()."</p>\n";
        pie();
        exit();
    }
}

// EJEMPLO DE USO DE LA FUNCIÓN ANTERIOR
// La conexión se debe realizar en cada página que acceda a la base de datos
$db = conectaDB();

Nota: En las soluciones de los ejercicios proporcionadas en estos apuntes, los archivos se guardan en el directorio /tmp/mclibre. Se debe crear ese directorio para que funcione las soluciones o cambiarlo a otro.


Conexión configurable

Si se incluyen ambas conexiones en el mismo programa, cada usuario puede elegir la base de datos más conveniente en cada caso. En el ejemplo siguiente, bastaría cambiar el valor de la variable $dbMotor para cambiar la base de datos utilizada.

// FUNCIÓN DE CONEXIÓN CON LA BASE DE DATOS MYSQL O CON SQLITE

define ('MYSQL', 'MySQL');
define ('SQLITE', 'SQLite');
$dbMotor = SQLITE;                                // Base de datos empleada
if ($dbMotor==MYSQL) {
    define('MYSQL_HOST', 'mysql:host=localhost'); // Nombre de host MYSQL
    define('MYSQL_USUARIO', 'root');              // Nombre de usuario de MySQL 
    define('MYSQL_PASSWORD', '');                 // Contraseña de usuario de MySQL
    $dbDb     = 'mclibre_baseDeDatos';            // Nombre de la base de datos
    $dbTabla = $dbDb.'.tabla';                    // Nombre de la tabla
} elseif ($dbMotor==SQLITE) {
    $dbDb     =  '/tmp/mclibre_baseDeDatos.sqlite';       // Nombre de la base de datos
    $dbTabla = 'tabla';                           // Nombre de la tabla
}

function conectaDb()
{
    global $dbMotor, $dbDb;
    
    try {
        if ($dbMotor==MYSQL) {
            $db = new PDO(MYSQL_HOST, MYSQL_USUARIO, MYSQL_PASSWORD);
            $db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, TRUE);
        } elseif ($dbMotor==SQLITE) {
            $db = new PDO('sqlite:'.$dbDb);
        }
        return($db);
    } catch (PDOException $e) {
        cabecera('Error grave');
        print "<p>Error: No puede conectarse con la base de datos.</p>\n";
//        print "<p>Error: " . $e->getMessage() . "</p>\n";
        pie();
        exit();
    }
}
// EJEMPLO DE USO DE LA FUNCIÓN ANTERIOR
// La conexión se debe realizar en cada página que acceda a la base de datos
$db = conectaDB();

Volver al principio de la página


Desconexión con la base de datos

Para desconectar con la base de datos hay que destruir el objeto PDO. Si no se destruye el objeto PDO, PHP lo destruye al terminar la página.

$db = NULL;

Volver al principio de la página


Consultas a la base de datos

Una vez realizada la conexión a la base de datos, las operaciones se realizan a través de consultas.

El método para efectuar consultas es PDO->query($consulta), que devuelve el resultado de la consulta. Dependiendo del tipo de consulta, el dato devuelto debe tratarse de formas distintas.


En los ejemplos, la consulta se realiza en dos líneas, pero podría estar en una sola:

// En dos líneas
$consulta = "SELECT * FROM $dbTabla";
$result = $db->query($consulta);

// En una sola línea
$result = $db->query("SELECT * FROM $dbTabla");

No hay motivo para preferir la primera versión, salvo que se quiera imprimir la consulta mientras se está programando para comprobar que no tiene errores:

$consulta = "SELECT * FROM $dbTabla";
print "<p>Consulta: $consulta</p>\n";
$result = $db->query($consulta);

Volver al principio de la página


Seguridad en las consultas: consultas preparadas

Para evitar ataques de inyección SQL (en la lección Inyecciones SQL se comentan los ataques más elementales), se recomienda el uso de sentencias preparadas, en las que PHP se encarga de "desinfectar" los datos en caso necesario.

Consultas preparadas

El método para efectuar consultas es primero preparar la consulta con PDO->prepare($consulta) y después ejecutarla con PDO->execute(array(parámetros)), que devuelve el resultado de la consulta. Dependiendo del tipo de consulta, el dato devuelto debe tratarse de formas distintas, como se ha explicado en el apartado anterior.


El siguiente ejemplo muestra cómo se realizaría una consulta (en tres líneas o en dos):

// En tres líneas
$consulta = "SELECT * FROM $dbTabla";
$result = $db->prepare($consulta);
$result->execute();

// En dos líneas
$result = $db->prepare("SELECT * FROM $dbTabla");
$result->execute();

No hay motivo para preferir la primera versión, salvo que se quiera imprimir la consulta mientras se está programando para comprobar que no tiene errores

$consulta = "SELECT * FROM $dbTabla";
print "<p>Consulta: $consulta</p>";
$result = $db->prepare($consulta);
$result->execute();

Si la consulta incluye datos introducidos por el usuario, los datos pueden incluirse directamente en la consulta, pero en ese caso, PHP no realiza ninguna "desinfección" de los datos, por lo que estaríamos corriendo riesgos de ataques:

$nombre    = $_REQUEST['nombre'];

$consulta = "SELECT COUNT(*) FROM $dbTabla 
    WHERE nombre=$nombre                       // DESACONSEJADO: PHP NO DESINFECTA LOS DATOS
    AND apellidos=$apellidos";                 // DESACONSEJADO: PHP NO DESINFECTA LOS DATOS
$result = $db->prepare($consulta);
$result->execute();
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
 ...

Para que PHP desinfecte los datos, estos deben enviarse al ejecutar la consulta, no al prepararla. Para ello es necesario indicar en la consulta la posición de los datos. Esto se puede hacer:

Se aconseja el uso de parámetros, ya que reduce la posibilidad de error.

Aunque no vayan a causar problermas en las consultas, sigue siendo conveniente tratar los datos recibidos para eliminar los espacios en blanco iniciales y finales, tratar los carácteres especiales del html, etc., como se comenta en la lección de Recogida de datos.

Una vez realizada la consulta, el tratamiento de la respuesta sería el mismo que se ha visto en el apartado anterior.


Restricciones en los parámetros de consultas preparadas

Debido a que las consultas preparadas se idearon para optimizar el rendimiento de las consultas, el uso de parámetros tiene algunas restricciones. Por ejemplo

Si no podemos usar parámetros, no queda más remedio que incluir los datos en la consulta. Como en ese caso PHP no hace ninguna desinfección de los datos, la tenemos que hacer nosotros previamente.

Como en estos casos los valores introducidos por el usuario suelen tener unos valores restringidos (por ejemplo, si el usuario puede elegir una columna de una tabla, los nombres de las columnas están determinadas y el usuario sólo puede elegir uno de ellos). Podemos crear una función de recogida de datos específica que impida cualquier tipo de ataque de inyección por parte del usuario, como muestra el siguiente ejemplo

// FUNCIÓN DE RECOGIDA DE UN DATO QUE SÓLO PUEDE TOMAR DETERMINADOS VALORES
$campos = array(
    'nombre',
    'apellidos');

function recogeCampo($var, $var2) 
{
    global $campos;

    foreach($campos as $campo) {
        if (isset($_REQUEST[$var]) && $_REQUEST[$var]==$campo) {
            return $campo;
        }
    }
    return $var2;
}

// EJEMPLO DE USO DE LA FUNCIÓN ANTERIOR
$campo  = recogeCampo('campo', 'apellidos'); 
$nombre = $_REQUEST['nombre'];

$consulta = "SELECT * FROM $dbTabla 
    WHERE nombre=:nombre
    ORDER BY $campo ASC";
$result = $db->prepare($consulta);
$result->execute(array(':nombre' => $nombre));
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
 ...

Volver al principio de la página


Ejemplos de consultas

En los ejemplos de este apartado, se han utilizado sentencias preparadas en los casos en los que las consultas incluyen datos proporcionados por el usuario y consultas no preparadas cuando no incluyan datos proporcionados por el usuario. En la mayoría de los casos se podrían haber gastado sentencias preparadas aunque no haya datos proporcionados por el usuario.

Recortar los datos

Una diferencia entre MySQL y SQLite es que si se guarda una cadena de mayor longitud que el campo correspondiente, MySQL recorta la cadena a la longitud del campo pero SQLite no lo hace. Para que los programas den el mismo resultado en MySQL y SQLite, una solución es recortar los datos introducidos por el usuario a la longitud del campo en la base de datos antes de insertarlos. Para ello se puede utilizar las funciones recorta() comentada en la lección de Recogida de datos.

Volver al principio de la página


Consultas CREATE DATABASE, DROP DATABASE

Nota: En el caso de utiliza SQLite, no tiene sentido crear o borrar la base de datos ya que con SQLite cada base de datos es un fichero distinto y al conectar con la base de datos ya se dice con qué archivo se va a trabajar y se crea en caso necesario.

Para crear una base de datos, se utiliza la consulta CREATE DATABASE.

// EJEMPLO DE CONSULTA DE CREACIÓN DE BASE DE DATOS
$db = conectaDb();
$consulta = "CREATE DATABASE $dbDb";
if ($db->query($consulta)) {
    print "<p>Base de datos creada correctamente.</p>\n";
} else {
    print "<p>Error al crear la base de datos.</p>\n";
}
$db = NULL;

Para borrar una base de datos, se utiliza la consulta DROP DATABASE.

// EJEMPLO DE CONSULTA DE BORRADO DE BASE DE DATOS
$db = conectaDb();
$consulta = "DROP DATABASE $dbDb";
if ($db->query($consulta)) {
    print "<p>Base de datos borrada correctamente.</p>\n";
} else {
    print "<p>Error al borrar la base de datos.</p>\n";
}
$db = NULL;

Volver al principio de la página


Consultas CREATE TABLE, DROP TABLE, INSERT INTO, UPDATE, DELETE FROM

Para crear una tabla, se utiliza la consulta CREATE TABLE. Las consultas de creación de tabla suelen ser específicas de cada base de datos. Los ejemplos no utilizan sentencias preparadas (en caso de utilizarse sentencias preparadas, las variables no podrían ir como parámetros por tratarse de sentencias DDL).

// EJEJMPLO DE CONSULTA DE CREACIÓN DE TABLA EN MYSQL
$db = conectaDb();
$consulta = "CREATE TABLE $dbTabla (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    nombre VARCHAR($tamNombre),
    apellidos VARCHAR($tamApellidos),
    PRIMARY KEY(id)
    )";
if ($db->query($consulta)) {
    print "<p>Tabla creada correctamente.</p>\n";
} else {
    print "<p>Error al crear la tabla.</p>\n";
}
$db = NULL;

// EJEMPLO DE CONSULTA DE CREACIÓN DE TABLA EN SQLite
$db = conectaDb();
$consulta = "CREATE TABLE $dbTabla (
    id INTEGER PRIMARY KEY,
    nombre VARCHAR($tamNombre),
    apellidos VARCHAR($tamApellidos)
    )";
if ($db->query($consulta)) {
    print "<p>Tabla creada correctamente.</p>\n";
} else {
    print "<p>Error al crear la tabla.</p>\n";
}
$db = NULL;

Para borrar una tabla, se utiliza la consulta DROP TABLE.

// EJEMPLO DE CONSULTA DE BORRADO DE TABLA
$db = conectaDb();
$consulta = "DROP TABLE $dbTabla";
if ($db->query($consulta)) {
    print "<p>Tabla borrada correctamente.</p>\n";
} else {
    print "<p>Error al borrar la tabla.</p>\n";
}
$db = NULL;

Para añadir un registro a una tabla, se utiliza la consulta INSERT INTO.

// EJEMPLO DE CONSULTA DE INSERCIÓN DE REGISTRO
$db = conectaDb();

$nombre    = recoge('nombre');
$apellidos = recoge('apellidos');

$consulta = "INSERT INTO $dbTabla 
    (nombre, apellidos)
    VALUES (:nombre, :apellidos)";
$result = $db->prepare($consulta);
if ($result->execute(array(':nombre' => $nombre, ':apellidos' => $apellidos))) {
    print "<p>Registro creado correctamente.</p>\n";
} else {
    print "<p>Error al crear el registro.</p>\n";
}

$db = NULL;

Para modificar un registro a una tabla, se utiliza la consulta UPDATE.

// EJEMPLO DE CONSULTA DE MODIFICACIÓN DE REGISTRO
$db = conectaDb();

$nombre    = recoge('nombre');
$apellidos = recoge('apellidos');
$id        = recoge('id');

$consulta = "UPDATE $dbTabla 
    SET nombre=:nombre, apellidos=:apellidos 
    WHERE id=:id";
$result = $db->prepare($consulta);
if ($result->execute(array(':nombre' => $nombre, ':apellidos' => $apellidos, ':id' => $id))) {
    print "<p>Registro modificado correctamente.</p>\n";
} else {
    print "<p>Error al modificar el registro.</p>\n";
}

$db = NULL; 

Para borrar un registro de una tabla, se utiliza la consulta DELETE FROM.

Nota: En el ejemplo, los registros a borrar se reciben en forma de matriz y se recorre la matriz borrando un elemento en cada iteración.

// EJEMPLO DE CONSULTA DE BORRADO DE REGISTRO
$db = conectaDb();

$id = recogeMatriz('id');

foreach ($id as $indice => $valor) {
    $consulta = "DELETE FROM $dbTabla 
        WHERE id=:indice";
    $result = $db->prepare($consulta);
    if ($result->execute(array(':indice' => $indice))) {
        print "<p>Registro borrado correctamente.</p>\n";
    } else {
        print "<p>Error al borrar el registro.</p>\n";
    }
}

$db = NULL;

Volver al principio de la página


Consulta SELECT

Para obtener registros que cumplan determinados criterios se utiliza la consulta SELECT.

// EJEMPLO DE CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT * FROM $dbTabla";
$result = $db->query($consulta);
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
} else {
    print "<p>Consulta ejecutada.</p>\n";
}
$db = NULL;

Para acceder a los registros devueltos por la consulta, se puede utilizar un bucle foreach.

// EJEMPLO DE CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT * FROM $dbTabla";
$result = $db->query($consulta);
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
} else {
    foreach ($result as $valor) {
        print "<p>Nombre: $valor[nombre] - Apellidos: $valor[apellidos]</p>\n";
    }
}
$db = NULL;

O también se puede utilizar la función PDOStatement->fetch()

// EJEMPLO DE CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT * FROM $dbTabla";
$result = $db->query($consulta);
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
} else {
    while ($fila = $result->fetch()) {
        print "<pre>\n"; 
        print_r($fila);
        print "</pre>\n"; 
    }
}
$db = NULL;

El problema es que si la consulta no devuelve ningún registro, estos dos métodos no escribirían nada. Por ello se recomienda hacer primero una consulta que cuente el número de resultados de la consulta y, si es mayor que cero, hacer la consulta.

El ejemplo siguiente utiliza la función PDOStatement->fetchColumn(), que devuelve la primera columna del primer resultado (que en este caso contiene el número de registros de la consulta).

// EJEMPLO DE CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT COUNT(*) FROM $dbTabla";
$result = $db->query($consulta);
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
} elseif ($result->fetchColumn()==0) {
    print "<p>No se ha creado todavía ningún registro en la tabla.</p>\n";
} else {
    $consulta = "SELECT * FROM $dbTabla";
    $result = $db->query($consulta);
    if (!$result) {
        print "<p>Error en la consulta.</p>\n";
    } else {
        foreach ($result as $valor) {
            print "<p>Nombre: $valor[nombre] - Apellidos: $valor[apellidos]</p>\n";
        }
    }
}
$db = NULL;

La consulta SELECT permite efectuar búsquedas en cadenas utilizando el condicional LIKE o NOT LIKE y los comodines _ (cualquier carácter) o % (cualquier número de caracteres). La primera consulta del ejemplo siguiente devolvería todos los registros en los que el primer apellido es Pérez, mientras que la segunda consulta devolvería todos los registros en los que el primer o segundo apellido es Pérez.

// EJEMPLO DE CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT COUNT(*) FROM $dbTabla 
    WHERE apellidos LIKE :apellidos";
$result = $db->prepare($consulta);
$result->execute(array(':apellidos' => "$apellidos%"));
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
} else {
    print "<p>Se han encontrado ".$result->fetchColumn())." registros.</p>\n";
}
$db = NULL;

// EJEMPLO DE CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT COUNT(*) FROM $dbTabla 
    WHERE apellidos LIKE :apellidos";
$result = $db->prepare($consulta);
$result->execute(array(':apellidos' => "%$apellidos%"));
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
} else {
    print "<p>Se han encontrado ".$result->fetchColumn())." registros.</p>\n";
}
$db = NULL;

Se pueden también realizar consultas de unión entre varias tablas (el ejemplo está sacado del ejercicio de Biblioteca):

Nota: Escribir como consulta preparada.

// EJEMPLO DE CONSULTA DE UNIÓN DE TABLAS
$db = conectaDb();
$consulta = "SELECT $dbPrestamos.id AS id, $dbUsuarios.nombre as nombre, 
    $dbUsuarios.apellidos as apellidos, $dbObras.titulo as titulo, 
    $dbPrestamos.prestado as prestado, $dbPrestamos.devuelto as devuelto 
    FROM $dbPrestamos, $dbUsuarios, $dbObras
    WHERE $dbPrestamos.id_usuario=$dbUsuarios.id AND 
    $dbPrestamos.id_obra=$dbObras.id and $dbPrestamos.devuelto='0000-00-00'
    ORDER BY $campo $orden";
$result = $db->query($consulta);
if (!$result) {
    print "<p>Error en la consulta.</p>\n";
} else {
   ...
}
$db = NULL;

Volver al principio de la página

Esta página forma parte del curso "Páginas web con PHP" disponible en http://www.mclibre.org
Autor: Bartolomé Sintes Marco
Última modificación de esta página: 24 de noviembre de 2011

Creative Commons License
Esta obra está bajo una licencia de Creative Commons Reconocimiento-CompartirIgual 3.0 España.