PHP Data Objects (PDO)

La extensión PDO (PHP Data Objects) permite acceder a distintas bases de datos (MySQL/MariaBD, PostgreSQL, Oracle, MS SQL Server, SQLite, Firebird, DB2, Informix, etc.) de una forma uniforme, lo que facilita la portabilidad de las aplicaciones. La portabilidad entre bases de datos no es absoluta debido por ejemplo a las diferencias entre diferentes bases de datos en aspectos como la forma de conectarse o la sintaxis del lenguaje de definición de datos. Para ilustrar esas diferencias y como tratarlas adecuadamente, se ofrecen ejemplos de código específicos para SQLite y para MySQL.

La extensión PDO no evalúa la corrección de las consultas SQL, aunque sí implementa algunas medidas de seguridad mediante consultas preparadas, como se comenta en el apartado correspondiente.

Conceptos básicos de PDO

PDO es una biblioteca de PHP orientada a objetos, pero se puede utilizar sin problemas en un programa no orientado a objetos. Tan solo será necesario utilizar la notación propia de la orientación orientada a objetos al manipular el objeto PDO.

Conexión con la base de datos

Para trabajar con una base de datos en nuestro programa, simplemente nos conectaremos con la base de datos creando un objeto PDO. La información necesaria para la conexión con la base de datos específica se incluye como uno o varios argumentos:

// CONEXIÓN CON LA BASE DE DATOS

$pdo = new PDO("informacion-para-la-conexion");

La información para la conexión consiste en primer lugar en el DSN (Data Source Name), que indica como mínimo el nombre de la base de datos (sqlite, mysql, pgsql, etc.) y su ubicación. En su caso, el segundo y tercer argumento son el nombre y contraseña del usuario de la base de datos.

Si por algún motivo PDO no puede conectarse con la base de datos, se genera una excepción. En los ejemplos y soluciones de ejercicios de estos apuntes cuando se produce esta excepción se muestra un mensaje en español y a continuación el código de error SQLSTATE y el mensaje de error específico del driver.

        print "    <p class=\"aviso\">Error: No puede conectarse con la base de datos. {$e->getMessage()}</p>\n";

Manejo de errores de PDO

PDO permite elegir al usuario entre tres formas distintas de gestionar los errores encontrado al realizar las consultas (normalmente por errores de sintaxis de SQL):

Hasta PHP 8.0, publicado en 2020, el modo predeterminado era ERRMODE_SILENT, pero desde esa versión es ERRMODE_EXCEPTION. En estos apuntes se utiliza el modo PDO::ERRMODE_SILENT, que se puede abordar con simples estructuras if .. else ..., mientras que el modo PDO::ERRMODE_EXCEPTION requiere estructuras try ... catch ... que no se utilizan en estos apuntes (excepto en la conexión con las bases de datos).

El modo de manejo de errores se selecciona activando el atributo PDO::ATTR_ERRMODE tras conectarse con la base de datos, como se comenta más adelante en esta lección en el apartado dedicado a la conexión con la base de datos:

      $tmp->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);

Ejecución de las consultas

Una vez realizada la conexión, las consultas SQL se realizar a través del objeto PDO. La ejecución de la consulta y el tratamiento de la respuesta se puede hacer de cuatro formas distintas, atendiendo a dos criterios:

Por un lado, se debe considerar si la consulta incluye información que nos ha llegado a través de un formulario. Por otro lado, se debe considerar si la consulta puede devolver registros:


Cuando la consulta falle, es conveniente mostrar al usuario el motivo del fallo. Para ello, podemos utilizar los métodos errorCode() y errorInfo(). errorCode() devuelve el código de error SQLSTATE definido en la norma ANSI SQL. errorInfo() devuelve una matriz con el código de error SQLSTATE, el código de error específico del driver y el mensaje de error específico del driver.

En los ejemplos y soluciones de ejercicios de estos apuntes cuando se produce un error se muestra un mensaje en español y a continuación el código de error SQLSTATE y el mensaje de error específico del driver.

    print "    <p class=\"aviso\">Error en la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";

En los ejemplos anteriores se ha escrito la consulta en la llamada al método, pero es habitual definir una variable $consulta que contenga la consulta y utilizar esa variable en la llamada al método:

// En una sola línea de programa

$resultado = $pdo->query("SELECT * FROM tabla");

// En dos líneas de programa

$consulta  = "SELECT * FROM tabla";
$resultado = $pdo->query($consulta);

Utilizar una variable auxiliar permite por ejemplo imprimir la consulta mientras se está programando para comprobar que no tiene errores.

$consulta = "SELECT * FROM tabla";
print "<p>Consulta: $consulta</p>\n";
// var_dump($consulta);
$resultado  = $pdo->query($consulta);

Gestión de los registros devueltos por la consulta

Cuando la consulta puede devolver registros, se debe guardar la respuesta en una variable auxiliar. Esa variable es de un tipo especial llamado recurso que no se puede acceder directamente, pero de la que podemos ir extrayendo uno a uno los registros mediante un bucle foreach () o mediante los métodos fetch() o fetchColumn():

Desconexión con la base de datos

Para desconectar con la base de datos, simplemente hay que destruir el objeto PDO.

$pdo = null;

Aunque no se destruya explícitamente el objeto PDO, PHP cierra la conexión al terminar de ejecutar la página, por lo que normalmente la instrucción no se suele escribir.

Consultas preparadas: seguridad en las consultas

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 PDO se encarga de "desinfectar" los datos. En general, cualquier consulta que incluya datos provenientes de un formulario (o en general, provenientes del usuario de la aplicación) debe realizarse mediante consultas preparadas.

Consultas preparadas

El método para efectuar consultas preparadas es primero preparar la consulta con PDO->prepare($consulta) y después ejecutarla con PDO->execute([parámetros]), que devuelve el resultado de la consulta.

// Consulta preparada

$consulta  = "SELECT * FROM tabla";
$resultado = $pdo->prepare($consulta);
$resultado->execute();

Si el programa incluye directamente en la consulta los datos recibidos a través de un formulario, PDO no puede hacer ninguna "desinfección" de los datos, ya que PDO no puede identificar qué parte de la consulta es la que ha llegado de un formulario y que puede haber manipulado la consulta original:

Desaconsejado
$valor1 = $_REQUEST["valor1"];
$valor2 = $_REQUEST["valor2"];

// ¡CUIDADO! DESACONSEJADO: PDO NO PUEDE DESINFECTAR LOS DATOS
$consulta = "SELECT COUNT(*) FROM tabla
             WHERE columna1=$valor1
             AND columna2=$valor2";

$resultado = $pdo->prepare($consulta);
if (!$resultado) {
    print "    <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute();) {
    print "    <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
    print "    <p>Se han encontrado {$resultado->fetchColumn()} registro(s).</p>\n";
    print "\n";
}

Para que PDO pueda desinfectar los datos provinientes de un formulario, estos deben enviarse al ejecutar la consulta, no al prepararla. Para ello, al definir la consulta es necesario indicar la posición de los datos y al ejecutar la consulta es necesario indicar el valor de los datos en forma de matriz. La posición de los datos se puede indicar de dos maneras distintas, mediante parámetros o mediante interrogantes (se aconseja la utilización de parámetros para evitar errores).

Notas:

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 desinfección 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 utilizar la función recoge() con los parámetros opcionales default y allowed que restringe los valores admitidos a los indicados. De esa manera impedimos los ataques de inyección por parte del usuario, como muestra el siguiente ejemplo:

// EJEMPLO DE RESTRICCIONES EN LOS PARÁMETROS DE CONSULTAS PREPARADAS

// Matriz con los nombres válidos de las columnas de la tabla
$columnas = [
    "columna1",
    "columna2",
    ...
];

$columna = recoge("columna", default: "columna1", allowed: $columnas);
$valor1  = $_REQUEST["valor1"];

// CONSULTA PREPARADA CON PARÁMETROS: PDO PUEDE DESINFECTAR LOS DATOS
$consulta = "SELECT * FROM tabla
             WHERE columna1=:valor1
             ORDER BY $columna ASC";

$resultado = $pdo->prepare($consulta);
if (!$resultado) {
    print "    <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute([":valor1" => $valor1]);) {
    print "    <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
    ...

Ejemplos de código para SQLite y MySQL

A continuación se ofrecen ejemplos de código de cómo se podría trabajar con PDO para realizar las tareas más habituales de gestión de una base de datos.

La base de datos de estos ejemplos tiene una estructura muy simple: una sola tabla personas con tres columnas: un identificador numérico (id), el nombre y los apellidos de una persona.

La estructura general de estas consultas se repite con ligeras variaciones. La estructura concreta viene impuesta por dos aspectos:

Opciones del programa

Para dotar de flexibilidad a la aplicación, la aplicación incluirá opciones de configuración en forma de variables. Distinguiremos entre opciones disponibles para el administrador de la aplicación y opciones disponibles para el programador. Aunque la distinción es a veces algo arbitraria, el objetivo es ayudar al administrador de la aplicación en su puesta en marcha:

Estas podrían ser las opciones para SQLite:

// SQLITE: OPCIONES DE CONFIGURACIÓN DEL PROGRAMA

// VARIABLES CONFIGURABLES POR EL ADMINISTRADOR DE LA APLICACIÓN

// Configuración para SQLite

$cfg["sqliteDatabase"] = "/tmp/db.sqlite";                        // Ubicación de la base de datos

Estas podrían ser las opciones para MySQL:

// MYSQL: OPCIONES DE CONFIGURACIÓN DEL PROGRAMA

// VARIABLES CONFIGURABLES POR EL ADMINISTRADOR DE LA APLICACIÓN

// Configuración para MySQL

$cfg["mysqlHost"]     = "localhost";                              // Nombre de host
$cfg["mysqlUser"]     = "";                                       // Nombre de usuario
$cfg["mysqlPassword"] = "";                                       // Contraseña de usuario
$cfg["mysqlDatabase"] = "";                                       // Nombre de la base de datos

Estas podrían ser las opciones comunes a ambas bases de datos:

// Variables configurables por el programador de la aplicación

// Nombres de las tablas

$cfg["tablaPersonas"] = "personas";           // Nombre de la tabla Personas

// Variables configurables por el administrador de la aplicación

// Tamaño de los campos en la tabla Personas

$cfg["tablaPersonasTamNombre"]    = 40;                 // Tamaño de la columna Personas > Nombre
$cfg["tablaPersonasTamApellidos"] = 60;                 // Tamaño de la columna Personas > Apellidos

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 el camino donde se quiere guardar la base de datos no exista.

Conexión con SQLite 3

Para poder utilizar SQLite mediante PDO, debe estar activada la extensión 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).

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.

Tenga en cuenta que PDO puede crear el archivo SQLite, pero no puede crear la ruta hasta el archivo, que debe existir previamente.

  • Si se indica simplemente el nombre del archivo SQLite, el archivo se creará en el mismo directorio que se encuentra el programa PHP. No se aconseja hacerlo así por dos motivos. En primer lugar, porque al guardar la base de datos en un directorio público, cualquier visitante podría obtener la base de datos completa escribiendo su url. En segundo lugar, porque si las páginas del programa se encuentran distribuidas en varios directorios, cada página accedería a una base de datos distinta creada en cada directorio. Por eso se recomienda guardar el archivo SQLite en un directorio fuera del directorio público, teniendo en cuenta que el directorio debe existir y el servidor web debe tener permisos de escritura en él.
// SQLITE: Conexión con la base de datos

function conectaDb()
{
    global $cfg;

    try {
        $tmp = new PDO("sqlite:$cfg[sqliteDatabase]");
        $tmp->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
        $tmp->query("PRAGMA foreign_keys = ON");
        $tmp->query("PRAGMA encoding = 'UTF-8'");
        return $tmp;
    } catch (PDOException $e) {
        print "    <p class=\"aviso\">Error: No puede conectarse con la base de datos. {$e->getMessage()}</p>\n";
        exit;
    }
}

// CONEXIÓN CON LA BASE DE DATOS
// La conexión se debe realizar en cada página que acceda a la base de datos
$pdo = conectaDb();

Notas:

  • El DSN para SQLite sólo permite indicar la ubicación de la base de datos SQLite (que puede ser un archivo físico, en memoria o en un fichero temporal gestionado por PHP). En estos apuntes se utiliza siempre un archivo físico.
  • El comando de SQLite "PRAGMA foreign keys = ON" es necesario para poder usar restricciones FOREIGN KEY en SQLite.
  • En las soluciones de los ejercicios proporcionadas en estos apuntes, los archivos se guardan en el directorio /tmp/. Para que funcionen las soluciones, se debe crear ese directorio o cambiarlo a otro.
  • En caso de error, la función conectaDb() imprime unos mensajes de error y termina el programa. Si quisiéramos completar una página correcta, deberíamos imprimir el pie de página en caso de error y asegurarnos de que se ha escrito la cabecera antes de conectarse a la base de datos.

Conexión con MySQL

En el caso de MySQL, para crear el objeto PDO se necesita proporcionar el nombre del servidor, el nombre de usuario de la base de datos y la contraseña. En el ejemplo siguiente esos datos se proporcionan como constantes que deberían definirse en el programa. El usuario de la base de datos se debe crear previamente.

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

// MYSQL: Conexión con la base de datos

function conectaDb()
{
    global $cfg;

    try {
        $tmp = new PDO("mysql:host=$cfg[mysqlHost];dbname=$cfg[mysqlDatabase];charset=utf8mb4", $cfg["mysqlUser"], $cfg["mysqlPassword"]);
    } catch (PDOException $e) {
        $tmp = new PDO("mysql:host=$cfg[mysqlHost];charset=utf8mb4", $cfg["mysqlUser"], $cfg["mysqlPassword"]);
    } catch (PDOException $e) {
        print "    <p class=\"aviso\">Error: No puede conectarse con la base de datos. {$e->getMessage()}</p>\n";
        exit;
    } finally {
        $tmp->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
        $tmp->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
        return $tmp;
    }
}

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

Notas:

  • El DSN para MySQL permite indicar el servidor en el que se encuentra la base de datos, su puerto, el nombre de la base de datos y el juego de caracteres. En estos apuntes se indican el servidor (localhost), el nombre de la base de datos y el juego de caracteres (utf8mb4). El nombre de usuario de la base de datos y su contraseña se añaden como segundo y tercer parámetros.
  • Si en la conexión se indica el nombre de la base de datos, en las consultas posteriores basta con indicar el nombre de la tabla afectada, mientras que si en la la conexión no se indica en nombre de la base de datos, en las consultas posteriores se debe indicar también el nombre de la base de datos en la que se encuentra la tabla afectada (siguiendo el formato nombreDB.nombreTabla). Para unificar las consultas en distintas nombres de bases de datos (SQLite no requiere nunca el nombre de la base de datos, PostgreSQL requiere indicar el nombre de la base de datos en la conexión y MariDB permite las dos formas), en estos apuntes se incluye el nombre de la base de datos en la conexión.
  • Si se indica el nombre de la base de datos en la conexión, un posible problema es que MariaDB requiere que la base de datos exista. Y si no existe, se produce una excepción. Para permitir que nuestros programas realicen la conexión aunque no exista la base de datos, la función intenta en segundo lugar conectarse sin indicar el nombre de la base de datos. En este caso habría que asegurarse que nuestros programas crean la base de datos antes de hacer consultas.

Creación y borrado de bases de datos y tablas: Consultas CREATE DATABASE, DROP DATABASE, CREATE TABLE, DROP TABLE

Las consultas de creación no son iguales en MySQL y SQLite. Para que una misma aplicación pueda trabajar con una u otra base de datos, en el apartado aplicación configurable estas consultas se incluyen en bibliotecas específicas.

Creación y borrado de tablas en SQLite

Con SQLite no tiene sentido crear o borrar la base de datos, ya que SQLite trabaja con ficheros y cada fichero es una base de datos. Tan solo es necesario borrar y crear las tablas.

Para borrar una tabla, se utiliza la consulta DROP TABLE. Esta consulta no es específica de SQlite y funcionaría con otras bases de datos. Se ha añadido la condición IF EXISTS porque en algunas versiones de SQLite la consulta DROP TABLE da error si la tabla no existe.

// CONSULTA DE BORRADO DE TABLA

$consulta = "DROP TABLE IF EXISTS $cfg[tablaPersonas]";

if (!$pdo->query($consulta)) {
    print "    <p class=\"aviso\">Error al borrar la tabla. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
    print "    <p>Tabla borrada correctamente (si existía).</p>\n";
}
print "\n";

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. El ejemplo no utiliza sentencias preparadas (en caso de utilizarse sentencias preparadas, las variables no podrían ir como parámetros por tratarse de sentencias DDL).

// SQLITE: CONSULTA DE CREACIÓN DE TABLA

$consulta = "CREATE TABLE $cfg[tablaPersonas] (
             id INTEGER PRIMARY KEY,
             nombre VARCHAR($cfg[tablaPersonasTamNombre]),
             apellidos VARCHAR($cfg[tablaPersonasTamApellidos])
             )";

if (!$pdo->query($consulta)) {
    print "    <p class=\"aviso\">Error al crear la tabla. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
    print "    <p>Tabla creada correctamente.</p>\n";
}
print "\n";

Creación y borrado de bases de datos y tablas en MySQL

Con MySQL, borraremos la base de datos (y las tablas existentes), crearemos de nuevo la base de datos y crearemos la tabla.

Para borrar una base de datos, se utiliza la consulta DROP DATABASE. Se ha añadido la condición IF EXISTS porque en algunas bases de datos la consulta DROP DATABASE da error si la tabla no existe.

// MYSQL: CONSULTA DE BORRADO DE BASE DE DATOS

$consulta = "DROP DATABASE IF EXISTS $cfg[mysqlDatabase]";

if (!$pdo->query($consulta)) {
    print "    <p class=\"aviso\">Error al borrar la base de datos. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
    print "    <p>Base de datos borrada correctamente (si existía).</p>\n";
}
print "\n";

Para crear una base de datos, se utiliza la consulta CREATE DATABASE. En el caso de MySQL es necesario indicar con una consulta USE que se desea trabajar con esa base de datos (independientemente de que nos hayamos conectado indicando el nombre de la base de datos en el DSN o no).

// MYSQL: CONSULTA DE CREACIÓN DE BASE DE DATOS

$consulta = "CREATE DATABASE $cfg[mysqlDatabase]
             CHARACTER SET utf8mb4
             COLLATE utf8mb4_unicode_ci";

if (!$pdo->query($consulta)) {
    print "    <p class=\"aviso\">Error al crear la base de datos. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
    print "    <p>Base de datos creada correctamente.</p>\n";
    print "\n";

    $consulta = "USE $cfg[mysqlDatabase]";

    if (!$pdo->query($consulta)) {
        print "    <p class=\"aviso\">Error en la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
    } else {
        print "    <p>Base de datos seleccionada correctamente.</p>\n";
        print "\n";
    }
}

Nota: El juego de caracteres utilizado en este curso es UTF-8, por lo que en la base de datos MySQL se utiliza el juego de caracteres utf8mb4 (que permite almacenar cualquier carácter Unicode) y el cotejamiento utf8mb4_unicode_ci (que implementa todos los criterios de ordenación de Unicode). Para una explicación más detallada se puede consultar el blog de Mathias Bynens.

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. El ejemplo no utiliza sentencias preparadas (en caso de utilizarse sentencias preparadas, las variables no podrían ir como parámetros por tratarse de sentencias DDL).

// MYSQL: CONSULTA DE CREACIÓN DE TABLA

$consulta = "CREATE TABLE $cfg[tablaPersonas] (
             id INTEGER UNSIGNED AUTO_INCREMENT,
             nombre VARCHAR($cfg[tablaPersonasTamNombre]),
             apellidos VARCHAR($cfg[tablaPersonasTamApellidos]),
             PRIMARY KEY(id)
             )";

if (!$pdo->query($consulta)) {
    print "    <p class=\"aviso\">Error al crear la tabla. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
    print "    <p>Tabla creada correctamente.</p>\n";
}

Creación, actualización y borrado de registros: Consultas INSERT INTO, UPDATE, DELETE FROM

Estas consultas son iguales en MySQL y SQLite.


Si el campo es de tipo numérico, y el valor a insertar es una cadena (como ocurre cuando recogemos el dato de un formulario), podríamos tener un problema de tipos al insertarlo. En el caso de los sistemas gestores de bases de datos que se usan en este curso (SQLite y MariaDB/MySQL de forma general, PostgreSQL en algún ejercicio) la conversión de tipos es automática y la cadena se convierte automáticamente en un valor numérico, pero puede que en otros sistemas la conversión no se realice y se produzca un error.

Si nos encontráramos con este problema, deberíamos realizar la conversión de cadena a entero. Para ello podríamos usar la función de PHP intval() como muestra el ejemplo siguiente en el que "edad" sería un campo INTEGER:

$consulta = "INSERT INTO $cfg[tablaPersonas]
             (nombre, edad)
             VALUES (:nombre, :edad)";

$resultado = $pdo->prepare($consulta);
if (!$resultado) {
    print "    <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute([":nombre" => $nombre, ":edad" => intval($edad)])) {
    print "    <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
    print "    <p>Registro creado correctamente.</p>\n";
}

En las soluciones de los ejercicios de estos apuntes no se realiza la conversión, ya que SQLite, MySQL o PostgreSQL no lo necesitan.

Selección de registros: Consulta SELECT

Para obtener registros que cumplan determinados criterios se utiliza una consulta SELECT. Estas consultas son iguales en MySQL y SQLite.

Selección de registros: Consulta SELECT LIKE

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). Estas consultas son iguales en MySQL y SQLite.

Ejemplos de consultas:


Si el campo es de tipo numérico, y la cadena de búsqueda es una cadena, podríamos tener un problema de tipos al compararlos. En el caso de muchas bases de datos (como SQLite o MySQL) la conversión de tipos es automática y el valor numérico de la base de datos se convierte automáticamente en una cadena para permitir la búsqueda la cadena de búsqueda, pero en otras (como PostgreSQL) la conversión no se realiza y se produce un error.

Para escribir consultas que se puedan ejecutar sin problemas en el mayor número de sistemas gestores de bases de datos, es recomendable realizar la conversión usando la función SQL como muestra el ejemplo siguiente en el que "edad" sería un campo INTEGER:

$consulta = "SELECT * FROM $cfg[tablaPersonas]
             WHERE nombre LIKE :nombre
             AND CAST(edad AS VARCHAR) LIKE :edad
             ORDER BY $ordena";

Consultas de unión de tablas

Se pueden también realizar consultas de unión entre varias tablas, como en el ejemplo siguiente. El ejemplo está sacado del ejercicio de Biblioteca y devuelve los préstamos pendientes de devolver a partir de tres tablas de Usuarios, Obras y Préstamos:

// CONSULTA DE UNIÓN DE TABLAS

$consulta = "SELECT
               $cfg[tablaPrestamos].id AS id,
               $cfg[tablaUsuarios].nombre as nombre,
               $cfg[tablaUsuarios].apellidos as apellidos,
               $cfg[tablaObras].titulo as titulo,
               $cfg[tablaPrestamos].prestado as prestado,
               $cfg[tablaPrestamos].devuelto as devuelto
             FROM $cfg[tablaPrestamos], $cfg[tablaUsuarios], $cfg[tablaObras]
             WHERE
               $cfg[tablaPrestamos].id_usuario=$cfg[tablaUsuarios].id
               AND $cfg[tablaPrestamos].id_obra=$cfg[tablaObras].id
               AND $cfg[tablaPrestamos].devuelto='0000-00-00'
             ORDER BY $columna $orden";

$resultado = $pdo->query($consulta);
if (!$resultado) {
    print "    <p class=\"aviso\">Error en la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} else {
   ...

Aplicación configurable

En los ejercicios en este curso se propone al alumno organizar los programas de manera que puedan trabajar tanto con SQLite como con MySQL y hacerlo de forma organizada, para que se puedan añadir fácilmente otras bases de datos.

Para ello distribuiremos los elementos comunes (constantes, variables y funciones) en cuatro ficheros distintos

Todas las páginas de la aplicación cargarán la biblioteca general, que a su vez cargará el fichero de configuración y una de las dos bibliotecas específicas, de acuerdo con la configuración escogida por el usuario.

Todas las páginas incluyen biblioteca.php biblioteca.php <?php ... include "config.php"; ... if (SQLite) { include "biblioteca-sqlite.php" } elseif (MySQL) { include "biblioteca-mysql.php" } ... include config.php include biblioteca-sqlite.php include biblioteca-mysql.php

Conexión configurable

Por ejemplo, para el caso de la función de conexión, el resultado sería:

Borrado y creación de base de datos y tablas configurable

El resultado podría ser el siguiente