# PHP 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.

- Si es una consulta que no devuelve registros, sino que simplemente realiza una acción que puede tener éxito o no (por ejemplo, insertar un registro), el método devuelve TRUE o FALSE. No es necesario guardar el resultado de la consulta en ninguna variable, pero se puede utilizar para sacar un mensaje diciendo que todo ha ido bien (o no). Por ejemplo,

      // EJEMPLO DE CONSULTA DE INSERCIÓN DE REGISTRO
      $db = conectaDb();
      $consulta = "INSERT INTO $dbTabla
          (nombre, apellidos)
          VALUES ('.$nombre.', '.$apellidos.')";
      if ($db->query($consulta)) {
          print "<p>Registro creado correctamente.</p>\n";
      } else {
          print "<p>Error al crear el registro.<p>\n";
      }
      $db = NULL;

- Pero si la consulta devuelve registros, el método devuelve los registros correspondientes o FALSE. En ese caso sí que es conveniente guardar lo que devuelve el método en una variable para procesarla posteriormente. Si contiene registros, la variable es de un tipo especial llamado recurso que no se puede acceder directamente, pero que se puede recorrer con un bucle foreach(),

      // EJEMPLO DE CONSULTA DE SELECCIÓN DE REGISTRO
      $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>$valor[nombre] $valor[apellidos]</p>\n";
          }
      }

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:

- mediantes interrogantes (?)

  En este caso la matriz debe incluir los valores que sustituyen a los interrogantes en el mismo orden en que aparecen en la consulta, como muestra el siguiente ejemplo:

      $nombre    = $_REQUEST['nombre'];
      $apellidos = $_REQUEST['apellidos'];

      $consulta = "SELECT COUNT(*) FROM $dbTabla
          WHERE nombre=?
          AND apellidos=?";
      $result = $db->prepare($consulta);
      $result->execute(array($nombre, $apellidos));
      if (!$result) {
          print "<p>Error en la consulta.</p>\n";
       ...

- mediante parámetros (:parametro)

  En este caso la matriz debe incluir los nombres de los parámetros y los valores que sustituyen a los parámetros (el orden no es importante), como muestra el siguiente ejemplo:

      $nombre    = $_REQUEST['nombre'];
      $apellidos = $_REQUEST['apellidos'];

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

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

- los identificadores (nombres de tablas, nombres de columnas, etc) no pueden sustituirse por parámetros
- los dos elementos de una igualdad no pueden sustituirse por parámetros
- en general no pueden utilizarse parátros en las consultas DDL (lenguaje de definición de datos) (nombre y tamaño de los campos, etc.)

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 obra está bajo una licencia de Creative Commons Reconocimiento-CompartirIgual 3.0 España.