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, PostgreSQL, SQLite, Firebird, MS SQL Server, DB2, Informix, Oracle).
La extensión PDO no evalúa la correción de las consultas SQL.
Los siguientes cuadros resumen las funciones y consultas utilizadas en el ejercicio de Agenda.
// FUNCIONES DE RECOGIDA DE DATOS QUE SE VAN A UTILIZAR EN UNA CONSULTA
function recogeParaConsulta($db, $var, $var2='')
{
$tmp = (isset($_REQUEST[$var])&&($_REQUEST[$var]!='')) ?
trim(strip_tags($_REQUEST[$var])) : trim(strip_tags($var2));
if (get_magic_quotes_gpc()) {
$tmp = stripslashes($tmp);
}
$tmp = str_replace('&', '&', $tmp);
$tmp = str_replace('"', '"', $tmp);
$tmp = recorta($var, $tmp);
if (!is_numeric($tmp)) {
$tmp = $db->quote($tmp);
}
return $tmp;
}
function recogeMatrizParaConsulta($db, $var)
{
$tmpMatriz = array();
if (isset($_REQUEST[$var]) && is_array($_REQUEST[$var])) {
foreach ($_REQUEST[$var] as $indice => $valor) {
$tmp = trim(strip_tags($indice));
if (get_magic_quotes_gpc()) {
$tmp = stripslashes($tmp);
}
$tmp = str_replace('&', '&', $tmp);
$tmp = str_replace('"', '"', $tmp);
$tmp = recorta($var, $tmp);
if (!is_numeric($tmp)) {
$tmp = $db->quote($tmp);
}
$indiceLimpio = $tmp;
$tmp = trim(strip_tags($valor));
if (get_magic_quotes_gpc()) {
$tmp = stripslashes($tmp);
}
$tmp = str_replace('&', '&', $tmp);
$tmp = str_replace('"', '"', $tmp);
$tmp = recorta($var, $tmp);
if (!is_numeric($tmp)) {
$tmp = $db->quote($tmp);
}
$valorLimpio = $tmp;
$tmpMatriz[$indiceLimpio] = $valorLimpio;
}
}
return $tmpMatriz;
}
Nota: La función recogeParaConsulta añade comillas al principio y al final.
Si se hay que quitar esas comillas, se puede utilizar esta función:
function quitaComillasExteriores($var)
{
if (is_string($var)) {
if (isset($var[0])&&($var[0]=="'")) {
$var = substr($var, 1, strlen($var)-1);
}
if (isset($var[strlen($var)-1])&&($var[strlen($var)-1]=="'")) {
$var = substr($var, 0, strlen($var)-1);
}
}
return $var;
}
Nota2: La función recogeParaConsulta llama a una función recorta() que permite limitar
la longitud de los datos recibidos.
$recorta = array(
'nombre' => $tamNombre,
'apellidos'=> $tamApellidos,
'telefono' => $tamTelefono,
'correo' => $tamCorreo);
function recorta($campo, $cadena)
{
global $recorta;
$tmp = isset($recorta[$campo]) ? substr($cadena, 0, $recorta[$campo]) : $cadena;
return $tmp;
}
// CONECTAR CON LA BASE DE DATOS (MysQL y 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 = 'baseDeDatos'; // Nombre de la base de datos
$dbTabla = $dbDb.'.tabla'; // Nombre de la tabla
} elseif ($dbMotor==SQLITE) {
$dbDb = '/tmp/baseDeDatos.sqlite3'; // Nombre y ruta 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";
pie();
exit();
}
}
$db = conectaDB();
// DESTRUIR LA CONEXIÓN CON LA BASE DE DATOS
$db = NULL;
// EJEMPLOS DE CONSULTAS
// Borrar la base de datos (innecesario en SQLite)
$consulta = "DROP DATABASE $dbDb"
// Crear la base de datos (innecesario en SQLite)
$consulta = "CREATE DATABASE $dbDb"
// Crear una tabla en MySQL o en SQLite
$consultaMysql = "CREATE TABLE $dbAgenda (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
nombre VARCHAR($tamNombre),
apellidos VARCHAR($tamApellidos),
telefono VARCHAR($tamTelefono),
correo VARCHAR($tamCorreo),
PRIMARY KEY(id)
)";
$consultaSqlite = "CREATE TABLE $dbAgenda (
id INTEGER PRIMARY KEY,
nombre VARCHAR($tamNombre),
apellidos VARCHAR($tamApellidos),
telefono VARCHAR($tamTelefono),
correo VARCHAR($tamCorreo)
)";
// Borrar una tabla
$consulta = "DROP TABLE $dbAgenda";
// Añadir un dato
$consulta = "INSERT INTO $dbAgenda
values (NULL, $nombre, $apellidos, $telefono, $correo)";
// Contar todos los registros
$consulta = "SELECT COUNT(*) FROM $dbAgenda";
// Seleccionar todos los registros
$consulta = "SELECT * FROM $dbAgenda ORDER BY $campo $orden";
// Borrar un registro
$consulta = "DELETE FROM $dbAgenda WHERE id=$indice";
// Borrar varios registros
$id = recogeMatrizParaConsulta($db, 'id');
foreach ($id as $indice => $valor) {
$consulta = "DELETE FROM $dbAgenda WHERE id=$indice";
...
}
// Buscar registros
$consulta = "SELECT * FROM $dbAgenda WHERE nombre LIKE '%$nombre%'
AND apellidos LIKE '%$apellidos%' AND telefono LIKE '%$telefono%'
AND correo LIKE '%$correo%' ORDER BY $campo $orden";
// Modificar un registro
$consulta = "UPDATE $dbAgenda SET nombre=$nombre,
apellidos=$apellidos, telefono=$telefono, correo=$correo
WHERE id=$id";
// EJECUTAR CONSULTAS
$result = $db->query($consulta);
// Para comprobar si el resultado es vacío:
$consulta = "SELECT COUNT(*) FROM $dbAgenda";
$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 agenda.</p>\n";
} else {
$consulta = "SELECT * FROM $dbAgenda ORDER BY $campo $orden";
$result = $db->query($consulta);
if (!$result) {
print "<p>Error en la consulta.</p>\n";
} else {
...
// Si el resultado de la consulta pueden ser varias filas:
foreach ($result as $valor) {
print "<p>Nombre: $valor[nombre] - Apellidos: $valor[apellidos]</p>\n";
}
Volver al principio de la página
Para conectar con la base de datos hay que crear una instancia de la clase PDO, que se utiliza en todas las consultas posteriores.
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 o a que no esté activada la extensión pdo.
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.
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();
}
}
$db = conectaDB();
En SQLite, cada base de datos es un archivo (que debe estar situado en un directorio en el que el servidor web tenga permisos de escritura).
Para poder utilizar SQLite mediante PDO, hay que activar en el archivo de configuración php.ini la extensión php_pdo_sqlite.dll (que en el caso de XAMPP está desactivada).
function conectaDb()
{
try {
$db = new PDO('sqlite:/tmp/archivo.sqlite3');
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();
}
}
$db = conectaDB();
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.
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_agenda'; // Nombre de la base de datos
$dbAgenda = $dbDb.'.agenda'; // Nombre de la tabla
} elseif ($dbMotor==SQLITE) {
$dbDb = '/tmp/mclibre/mclibre_agenda.sqlite3'; // Nombre de la base de datos
$dbAgenda = 'agenda'; // 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();
}
}
$db = conectaDB();
Volver al principio de la página
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 el script.
$db = NULL;
Volver al principio de la página
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.
$db = conectaDb();
$consulta = "SELECT * FROM $dbAgenda";
$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";
}
}
Volver al principio de la página
Los ataques malintencionados más habituales son las inyecciones de SQL, que consisten en enviar en los campos de formulario partes de una consulta SQL de manera que PHP ejecute una consulta distinta a la preparada por el programador de la página.
Para evitar este tipo de ataques, es necesario utilizar el método PDO->quote($consulta) antes de incluir en la consulta datos enviados por el usuario. Este método debe aplicarse a cualquier dato recibido del usuario que vaya a incluirse en una consulta. Las funciones del ejemplo siguiente aplican, además del método quote, todas las precacuciones comentadas en la lección de recogida de datos, para recoger datos sueltos o matrices de una dimensión.
function recogeParaConsulta($db, $var, $var2='')
{
$tmp = (isset($_REQUEST[$var])&&($_REQUEST[$var]!='')) ?
trim(strip_tags($_REQUEST[$var])) : trim(strip_tags($var2));
if (get_magic_quotes_gpc()) {
$tmp = stripslashes($tmp);
}
$tmp = str_replace('&', '&', $tmp);
$tmp = str_replace('"', '"', $tmp);
$tmp = recorta($var, $tmp);
if (!is_numeric($tmp)) {
$tmp = $db->quote($tmp);
}
return $tmp;
}
function recogeMatrizParaConsulta($db, $var)
{
$tmpMatriz = array();
if (isset($_REQUEST[$var]) && is_array($_REQUEST[$var])) {
foreach ($_REQUEST[$var] as $indice => $valor) {
$tmp = trim(strip_tags($indice));
if (get_magic_quotes_gpc()) {
$tmp = stripslashes($tmp);
}
$tmp = str_replace('&', '&', $tmp);
$tmp = str_replace('"', '"', $tmp);
$tmp = recorta($var, $tmp);
if (!is_numeric($tmp)) {
$tmp = $db->quote($tmp);
}
$indiceLimpio = $tmp;
$tmp = trim(strip_tags($valor));
if (get_magic_quotes_gpc()) {
$tmp = stripslashes($tmp);
}
$tmp = str_replace('&', '&', $tmp);
$tmp = str_replace('"', '"', $tmp);
$tmp = recorta($var, $tmp);
if (!is_numeric($tmp)) {
$tmp = $db->quote($tmp);
}
$valorLimpio = $tmp;
$tmpMatriz[$indiceLimpio] = $valorLimpio;
}
}
return $tmpMatriz;
}
Estas funciones añaden comillas al principio y al final, pero en algunos casos los datos insertados en las consultas no deben estar entre comillas. Esta función quita esas comillas:
function quitaComillasExteriores($var)
{
if (is_string($var)) {
if (isset($var[0])&&($var[0]=="'")) {
$var = substr($var, 1, strlen($var)-1);
}
if (isset($var[strlen($var)-1])&&($var[strlen($var)-1]=="'")) {
$var = substr($var, 0, strlen($var)-1);
}
}
return $var;
}
Volver al principio de la página
Nota: En el caso de utiliza SQLite, no es necesario crear 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.
// CONSULTA DE CREACIÓN DE BASE DE DATOS
$db = conectaDb();
$consulta = "CREATE 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;
Para borrar una base de datos, se utiliza la consulta DROP DATABASE.
// 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
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.
// CONSULTA DE CREACIÓN DE TABLA EN MYSQL
$db = conectaDb();
$consulta = "CREATE TABLE $dbAgenda (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
nombre VARCHAR($tamNombre),
apellidos VARCHAR($tamApellidos),
telefono VARCHAR($tamTelefono),
correo VARCHAR($tamCorreo),
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;
// CONSULTA DE CREACIÓN DE TABLA EN SQLite
$db = conectaDb();
$consulta = "CREATE TABLE $dbAgenda (
id INTEGER PRIMARY KEY,
nombre VARCHAR($tamNombre),
apellidos VARCHAR($tamApellidos),
telefono VARCHAR($tamTelefono),
correo VARCHAR($tamCorreo)
)";
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.
// CONSULTA DE BORRADO DE TABLA
$db = conectaDb();
$consulta = "DROP TABLE $dbAgenda";
if ($db->query($consulta)) {
print "<p>Tabla de Agenda borrada correctamente.</p>\n";
} else {
print "<p>Error al borrar la tabla de Agenda.</p>\n";
}
$db = NULL;
Para añadir un registro a una tabla, se utiliza la consulta INSERT INTO.
// CONSULTA DE INSERCIÓN DE REGISTRO
$db = conectaDb();
$consulta = "INSERT INTO $dbAgenda
values (NULL, $nombre, $apellidos, $telefono, $correo)";
if ($db->query($consulta)) {
print "<p>Registro creado correctamente.</p>\n";
} else {
print "<p>Error al crear el registro.<p>";
}
$db = NULL;
Nota: Antes de insertar datos provinientes de un formulario, las cadenas deben haberse tratado para evitar inyecciones de SQL.
Para modificar un registro a una tabla, se utiliza la consulta UPDATE.
// CONSULTA DE MODIFICACIÓN DE REGISTRO
$db = conectaDb();
$consulta = "UPDATE $dbAgenda SET nombre=$nombre,
apellidos=$apellidos, telefono=$telefono, correo=$correo
WHERE id=$modificar";
if ($db->query($consulta)) {
print "<p>Registro modificado correctamente.</p>\n";
} else {
print "<p>Error al modificar el registro.</p>\n";
}
$db = NULL;
Nota: Antes de insertar datos provinientes de un formulario, las cadenas deben haberse tratado para evitar inyecciones de SQL.
Para borrar un registro de una tabla, se utiliza la consulta DELETE FROM.
// CONSULTA DE BORRADO DE REGISTRO
$db = conectaDb();
$consulta = "DELETE FROM $dbAgenda WHERE id=$indice";
if ($db->query($consulta)) {
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
Para obtener registros que cumplan determinados criterios se utiliza la consulta SELECT.
// CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT * FROM $dbAgenda";
$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.
// CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT * FROM $dbAgenda";
$result = $db->query($consulta);
if (!$result) {
print "<p>Error en la consulta.</p>\n";
} else {
foreach ($result as $valor) {
print "<pre>\n";
print_r($valor);
print "</pre>\n";
}
}
$db = NULL;
O también se puede utilizar la función PDOStatement->fetch()
// CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT * FROM $dbAgenda";
$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, en su caso, haga 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).
// CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT COUNT(*) FROM $dbAgenda";
$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 agenda.</p>\n";
} else {
$consulta = "SELECT * FROM $dbAgenda";
$result = $db->query($consulta);
if (!$result) {
print "<p>Error en la consulta.</p>\n";
} else {
foreach ($result as $valor) {
print "<pre>\n";
print_r($valor);
print "</pre>\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.
// CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT COUNT(*) FROM $dbAgenda WHERE apellidos LIKE '$apellidos%'";
$result = $db->query($consulta);
if (!$result) {
print "<p>Error en la consulta.</p>\n";
} else {
print "<p>Se han encontrado ".$result->fetchColumn())." registros.</p>\n";
}
$db = NULL;
// CONSULTA DE SELECCIÓN DE REGISTROS
$db = conectaDb();
$consulta = "SELECT COUNT(*) FROM $dbAgenda WHERE apellidos LIKE '%$apellidos%'";
$result = $db->query($consulta);
if (!$result) {
print "<p>Error en la consulta.</p>\n";
} else {
print "<p>Se han encontrado ".$result->fetchColumn())." registros.</p>\n";
}
$db = NULL;
En este caso los datos recogidos mediante la función recogeParaConsulta ($apellidos, etc) no deben llevar comillas ni al principio ni al final, por lo que deberían pasarse por la función quitaComillasExteriores().
Se pueden también realizar consultas de unión entre varias tablas:
// 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
Esta obra está bajo una licencia de Creative
Commons.