DWES03. - Trabajar Con Bases de Datos en PHP
DWES03. - Trabajar Con Bases de Datos en PHP
Caso práctico
Una de las tareas prioritarias que tienen que
abordar en el nuevo proyecto de BK
Programación es el almacenamiento de la
información que utilizará la aplicación web, y el
método de acceso que se utilizará para
manejarla desde PHP.
Caso práctico
De los distintos SGBD existentes, vas a aprender a utilizar MySQL. MySQL es un gestor de
bases de datos relacionales de código abierto bajo licencia GNU GPL. Es el gestor de
bases de datos más empleado con el lenguaje PHP. Como ya vimos, es la letra "M" que
figura en los acrónimos AMP y XAMPP.
En esta unidad vas a ver cómo acceder desde PHP a bases de datos MySQL utilizando
tanto PDO como la extensión nativa MySQLi. Previamente verás una pequeña introducción
al manejo de MySQL, aunque para el seguimiento de esta unidad se supone que conoces
el lenguaje SQL utilizado en la gestión de bases de datos relacionales.
En PHP se utiliza la palabra new para crear un nuevo objeto instanciando una clase:
$a = new A();
Y para acceder a los miembros de un objeto, debes utilizar el operador flecha ->:
$a->fecha();
Debes conocer
Es importante conocer las características más básicas de la utilización objetos
en PHP.
Caso práctico
Juan y Carlos deciden comenzar revisando el
servidor que van a utilizar, MySQL. Aunque van
a utilizar un servidor que ya está en
funcionamiento, deben comprender sus
capacidades y las herramientas de las que
disponen para poder gestionar tanto el servidor
como los datos que almacena.
MySQL se emplea en múltiples aplicaciones web, ligado en la mayor parte de los casos
al lenguaje PHP y al servidor web Apache. Utiliza SQL para la gestión, consulta y
modificación de la información almacenada. Soporta la mayor parte de las características
de ANSI SQL 99, y añade además algunas extensiones propias.
Autoevaluación
¿A qué hacen referencia las siglas PDO?
A un motor de almacenamiento utilizado por MariaDB o MySQL.
Solución
1. Incorrecto
2. Opción correcta
2.1.- Instalación y configuración.
Una vez instalado, puedes gestionar la ejecución del servicio de la misma forma que
cualquier otro servicio del sistema:
Si hemos instalado LAMP en Ubuntu, por defecto, MySQL/MariaDB viene sin password
para el usuario root y con un plugin de autentificación (auth_socket) que nos obliga por defecto
a usar un usuario de Ubuntu con permiso de administrador, es decir la forma de entrar
como usuario root en MySQL será: sudo mysql -u root . La recomendación es, que esta
configuración no se cambie. Los pasos recomendados cuando empecemos un
proyecto PHP el que necesitemos una Base de Datos y acceso a ella serán:
Nos conectamos como root a MySQL: sudo mysql -u root (terminal linux).
Creamos la base de datos.: create database proyecto; (estamos en MySQL/MariaDB).
Creamos un usuario y le asigno un password: create user usuario@'localhost' identified by
"password"; (estamos en MySQL/MariaDB).
Le doy permiso al usuario en la base de datos anterior: grant all on proyecto.* to
usuario@'localhost'; (estamos en MySQL/MariaDB).
Salimos: quit; (estamos en MySQL/MariaDB y salimos a la terminal)
Comprobamos que todo funciona conectándonos con este usuario a la bases de datos
creada: mysql -u usuario -p proyecto (terminal linux).
Luego ya podemos usar PhpMyAdmin, o cualquier otro programa, con las credenciales de
este usuario para administrar la base de datos.
El servidor se ejecuta por defecto en el Puerto TCP 3306. Esto lo debes tener en cuenta
para permitir el acceso a través del cortafuegos en configuraciones en red.
[client].
Sus parámetros influyen sobre los distintos clientes que se conectan al servidor
MySQL.
[mysqld]. Contiene opciones relativas a la ejecución del servidor.
Por ejemplo, para establecer una conexión al servidor local con la herramienta mysql,
podemos hacer:
Recomendación
Conviene no indicar nunca la contraseña en la misma línea de comandos. En
caso de que la cuenta esté convenientemente protegida por una contraseña,
es mejor utilizar solo la opción -p como en el ejemplo anterior. De esta forma,
la herramienta solicita la introducción de la contraseña y ésta no queda
almacenada en ningún registro como puede ser el historial de comandos del
sistema.
Debes conocer
De entre el resto de herramientas de administración independientes que
podemos utilizar con MySQL, podemos destacar dos:
MySQL Workbench.
phpMyAdmin.
Autoevaluación
Relaciona cada herramienta de administración con el tipo de interface
que utiliza:
Ejercicio de relacionar
Herramienta. Relación. Tipo de interface.
mysql. 2. Web.
phpMyAdmin. 3. Nativo.
Enviar
Es importante conocer las diferentes herramientas de administración de
MySQL.
2.2.1.- mysql y mysqladmin.
MySQL.
use. Permite seleccionar una base de datos.
exit o quit. Termina la sesión interactiva con MySQL.
help. Muestra una pantalla de ayuda con la lista de comandos disponibles.
Por ejemplo, si cuando estás utilizando la herramienta quieres seleccionar la base de datos
"dwes", debes hacer: mysql>use dwes;
Las sentencias que teclees a partir de ese instante se ejecutarán sobre la base de datos
"dwes".
También puedes usar el comando mysql para que ejecute todas las sentencias de un
archivo de procesamiento por lotes (normalmente con extensión .sql). Por ejemplo y
suponiendo que nos encontremos en el directorio donde se encuentra el archivo crearTablas.sql
y queremos que este archivo se ejecute en la base de datos proyecto:
Si todo ha ido bien volveremos a la consola en la que estemos trabajando, no nos aparece
ningún mensaje, si algo ha ido mal nos aparecerá el mensaje de error.
Recomendación
¿Podemos o no almacenar "emojis" en MySQL?, la respuesta es si, pero no
utilizando el "utf-8" que normalmente se usaba.
En 2010 (Con su versión 5.5.3) MySQL agrega una variante a "utf-8" llamada
"utf8mb4". Con este nuevo tipo de codificación, cada carácter puede ser
representado hasta 4 bytes, lo que nos permitirá guardar "emojis" en nuestras
tablas algo cada vez más necesario.
Ejercicio resuelto
Crearemos como usuario MySQL root y
usando un archivo de procesamiento por
lotes, la base de datos proyecto. En ella
meteremos las tablas del siguiente
esquema, fíjate en los campos y las
relaciones entre ellas. De igual manera en
dicho archivo crearemos el usuario "gestor"
con contraseña "secreto" y le daremos
todos los permisos en la base de datos
"proyecto". Las tablas serán:
Captura de pantalla de phpMyAdmin (Elaboración
propia)
1.- productos (id, nombre, nombre_corto, descripcion, pvp,
familia)
2.- tiendas (id, nombre, tlf)
3.- familias (cod, nombre)
4.- stocks (producto, tienda, unidades)
Es decir el archivo ".sql", estará todo el código para crear la base de datos, las
tablas, el usuario y los permisos del mismo para acceder a ella.
La solución deberá ser el archivo ".sql" y el comando, que tenemos que usar
en el terminal, para "cargar" el archivo en la base de datos MySQL.
Mostrar retroalimentación
Por ejemplo, si quieres mostrar información sobre el estado actual del servidor local,
puedes utilizar el comando "status" y para ver la versión instalada "version", puedes ver los
comandos disponible usando "mysqladmin --help":
mysqladmin --help
sudo mysqladmin status
sudo mysqladmin version
Fíjate que los comandos anteriores no necesitan especificar usuario por que son solamente
de información. Si quisiésemos, por ejemplo crear una base de datos, borrarla, reiniciar el
servidor... con mysqladmin, deberíamos hacerlo con sudo y especificando el usuario root. Por
ejemplo para crear una base de datos de nombre miBase:
Autoevaluación
Si quieres saber si en una tabla de una base de datos existe o no un
registro, ¿qué herramienta en línea de comandos puedes usar?
mysqladmin.
mysql.
Solución
1. Incorrecto
2. Opción correcta
2.2.2.- phpMyAdmin.
En ambos casos para poder entrar, debes indicar un nombre de usuario y contraseña
válidos. Si realizaste el ejercicio anterior, se habrá creado en tu servidor un usuario "gestor"
con contraseña "secreto" con todos los permisos para la base de datos "proyecto". Si
utilizas ese usuario para entrar en la aplicación, ésta te permitirá gestionar la base de datos
"proyecto".
Una vez creado ya podemos usar las credenciales admin, secreto para entrar en
phpMyAdmin como un usuario de MySQL con todos los permisos (y por ello mucha
precaución).
Si seleccionas una tabla en lugar de la base de datos, podrás efectuar a ese nivel
operaciones similares a las anteriores. En la siguiente presentación sobre phpMyAdmin
tienes información sobre el maneja básico de la aplicación.
◄ ►
Página de Login
Página de inicio
Si pinchamos en proyecto podremos ver las tablas que contiene y una serie de
operaciones sobre ellas.
Podemos fácilmente ver la estructura de una tabla y sus índices, podemos borrar,
modificar y crear nuevos campos fácilmente.
Generando Consultas
Exportando tablas
Diseñador
De un vistazo podemos ver la estructura de nuestra base de datos, las tablas que
contiene y como están relacionadas.
1 2 3 4 5 6 7 8
Mostrar retroalimentación
Caso práctico
Entre María, Juan y Carlos, han creado una
pequeña base de datos con cuatro tablas y unas
decenas de registros que usarán en las pruebas
de la nueva aplicación web.
En las aplicaciones que había realizado hace ya algunos años, siempre había
utilizado la misma extensión. Y ahora, por lo que ha estado viendo, existen
otras maneras más eficientes o más genéricas de llevar a cabo esa tarea.
Con cualquiera de ambas extensiones, podrás realizar acciones sobre las bases de datos
como:
Establecer conexiones.
Ejecutar sentencias SQL.
Obtener los registros afectados o devueltos por una sentencia SQL.
Emplear transacciones.
Ejecutar procedimientos almacenados.
Gestionar los errores que se produzcan durante la conexión o en el establecimiento
de la misma.
PDO y mysqli (y también la antigua extensión mysql) utilizan un driver de bajo nivel para
comunicarse con el servidor MySQL. Hasta hace poco el único driver disponible para
realizar esta función era libmysql, que no estaba optimizado para ser utilizado desde PHP. A
partir de la versión 5.3, viene preparado para utilizar también un nuevo driver mejorado
para realizar esta función, el driver nativo de MySQL, mysqlnd.
3.1.- Extensión MySQLi.
Manual de PHP.
Entre las mejoras que aporta respecto a la antigua extensión mysql, figuran:
Autoevaluación
¿Qué interface o interfaces de programación admite la extensión
MySQLi?
Solución
1. Incorrecto
2. Opción correcta
3.1.1.- Establecimiento de conexiones.
Aunque también tienes la opción de primero crear la instancia, y después utilizar el método
"connect" para establecer la conexión con el servidor:
Por ejemplo, el siguiente código comprueba el establecimiento de una conexión con la base
de datos "proyecto" y finaliza la ejecución si se produce algún error:
Observa que, como veremos posteriormente con más detalle, puedes anteponer
a cualquier expresión el operador de control de errores "@" para que se ignore
cualquier posible error que pueda producirse al ejecutarla.
Si una vez establecida la conexión, quieres cambiar la base de datos puedes usar el
método "select_db" (o la función "mysqli_select_db" de forma equivalente) para indicar el nombre
de la nueva, lógicamente el usuario con el que hemos iniciado la conexión debe tener
permisos en la nueva.
$conProyecto->select_db('otra_bd');
Una vez finalizadas las tareas con la base de datos, utiliza el método "close" (o la función
"mysqli_close") para cerrar la conexión con la base de datos y liberar los recursos que utiliza.
$conProyecto->close();
3.1.2.- Ejecución de consultas.
En el caso de ejecutar una sentencia SQL que sí devuelva datos (como un SELECT), éstos
se devuelven en forma de un objeto resultado (de la clase "mysqli_result"). En el punto
siguiente verás cómo se pueden manejar los resultados obtenidos.
El método "query()" tiene un parámetro opcional que afecta a cómo se obtienen internamente
los resultados, pero no a la forma de utilizarlos posteriormente. En la opción por defecto,
MYSQLI_STORE_RESULT, los resultados se recuperan todos juntos de la base de datos y se
almacenan de forma local. Si cambiamos esta opción por el valor MYSQLI_USE_RESULT, los
datos se van recuperando del servidor según se vayan necesitando.
Debes conocer
Otra forma que puedes utilizar para ejecutar una consulta es el método
real_query (o la función mysqli_real_query), que siempre devuelve true o false según
se haya ejecutado correctamente o no. Si la consulta devuelve un conjunto de
resultados, se podrán recuperar de forma completa utilizando el método
store_result, o según vaya siendo necesario gracias al método use_result.
Método real_query.
Es importante tener en cuenta que los resultados obtenidos se almacenarán en memoria
mientras los estés usando. Cuando ya no los necesites, los puedes liberar con el método
free de la clase mysqli_result (o con la función mysqli_free_result):
$resultado->free();
Autoevaluación
De las dos opciones que admite el método query, MYSQLI_STORE_RESULT y
MYSQLI_USE_RESULT, ¿qué opción será recomendable utilizar para ejecutar
una consulta que devuelva una enorme cantidad de datos?
MYSQLI_STORE_RESULT.
MYSQLI_USE_RESULT.
Incorrecto. ¿Qué sucede con los datos cuándo se utiliza esta opción?
Efectivamente. Con esta opción se van obteniendo los datos del servidor
a medida que se vayan necesitando. Si utilizaras la otra opción, los datos
tendrían que transferirse todos juntos al ejecutar la consulta.
Solución
1. Incorrecto
2. Opción correcta
3.1.3.- Transacciones.
...
Ejercicio resuelto
Según la información que figura en la tabla stock de la base de datos
proyecto, la tienda 1 (CENTRAL) tiene 2 unidades del producto de código
3DSNG y la tienda 3 (SUCURSAL2) ninguno. Suponiendo que los datos son
esos (no hace falta que los compruebes en el código), utiliza una transacción
para mover una unidad de ese producto de la tienda 1 a la tienda 3.
Mostrar retroalimentación
Deberás hacer una consulta de actualización (para poner unidades=1
en la tienda 1) y otra de inserción (pues no existe ningún registro
previo para la tienda 3). Observa el código de la solución. Comprueba
que se ejecuta bien solo la primera vez, pues en ejecuciones
posteriores ya no es posible insertar la misma fila en la tabla.
Autoevaluación
En el modo de gestión de transacciones que se utiliza por defecto, ¿es
posible revertir los cambios que se aplican al ejecutar una consulta de
acción?
No.
Sí.
Solución
1. Opción correcta
2. Incorrecto
3.1.4.- Obtención y utilización de
conjuntos de resultados.
Ya sabes que al ejecutar una consulta que devuelve datos obtienes
un objeto de la clase mysqli_result. Esta clase sigue los criterios de
ofrecer un interface de programación dual, es decir, una función por
cada método con la misma funcionalidad que éste.
Para trabajar con los datos obtenidos del servidor, tienes varias
posibilidades:
Parar recorrer todos los registros de un array, puedes hacer un bucle teniendo en cuenta
que cualquiera de los métodos o funciones anteriores devolverán null cuando no haya más
registros en el conjunto de resultados.
$stock = $resultado->fetch_object();
Clase mysqli_result.
Recomendación
A la hora de empezar a elaborar proyectos más complejos, cuidar
la presentación de las páginas es muy importante y el CSS nos
puede llevar mucho tiempo, por eso se recomienda
encarecidamente que uses frameworks de hojas de estilos ya
diseñadas como Bootstrap.
Bootstrap
(Dominio
público) Enlace a Documentación Bootstrap
Ejercicio resuelto
Crea una página web en la que se muestren las unidades existentes de un
determinado producto en cada una de las tiendas. Para seleccionar el
producto concreto utiliza un cuadro de selección dentro de un formulario en
esa misma página. Puedes usar como base los siguientes ficheros.
Mostrar retroalimentación
Revisa la solución propuesta y verifica que los resultados que obtuviste
son correctos.
Descargar Solución (pdf - 41,86 KB)
3.1.5.- Consultas preparadas.
Cada vez que se envía una consulta al servidor, éste debe analizarla
antes de ejecutarla. Algunas sentencias SQL, como las que insertan
valores en una tabla, deben repetirse de forma habitual en un
programa. Para acelerar este proceso, MySQL admite consultas
preparadas. Estas consultas se almacenan en el servidor listas para
ser ejecutadas cuando sea necesario.
Por otra parte existe un riesgo de seguridad muy importante al usar Troy Hunt (Dominio público)
formularios para insertar, consultar, modificar, borrar datos en una
base de datos, la "inyección SQL" . Unos de los métodos que se recomiendan para evitar
este tipo de ataques es precisamente usar consultas parametrizadas ya que los valores de
los parámetros, son transmitidos después, usando un protocolo diferente y no necesitan ser
escapados.
Para trabajar con consultas preparadas con la extensión MySQLi de PHP, debes utilizar la
clase mysqli_stmt. Utilizando el método stmt_init de la clase mysqli (o la función mysqli_stmt_init)
obtienes un objeto de dicha clase.
$stmt = $conProyecto->stmt_init();
Los pasos que debes seguir para ejecutar una consulta preparada son:
Por ejemplo, para preparar y ejecutar una consulta que inserta un nuevo registro en la tabla
familia:
$stmt = $conProyecto->stmt_init();
$stmt->prepare('INSERT INTO familias (cod, nombre) VALUES ("TABLET", "Tablet PC")');
$stmt->execute();
$stmt->close();
$conProyecto->close();
El problema que ya habrás observado, es que de poco sirve preparar una consulta de
inserción de datos como la anterior, si los valores que inserta son siempre los mismos. Por
este motivo las consultas preparadas admiten parámetros. Para preparar una consulta con
parámetros, en lugar de poner los valores debes indicar con un signo de interrogación su
posición dentro de la sentencia SQL.
$stmt->prepare('INSERT INTO familias (cod, nombre) VALUES (?, ?)');
i. Número entero.
s. Cadena de texto.
En el caso anterior, si almacenas los valores a insertar en sendas variables, puedes hacer:
$stmt = $conProyecto->stmt_init();
$stmt->prepare('INSERT INTO familias (cod, nombre) VALUES (?, ?)');
$cod_producto = "TABLET";
$nombre_producto = "Tablet PC";
$stmt->bind_param('ss', $cod_producto, $nombre_producto);
$stmt->execute();
$stmt->close();
$conProyecto->close();
Cuando uses bind_param para enlazar los parámetros de una consulta preparada
con sus respectivos valores, deberás usar siempre variables como en el ejemplo
anterior. Si intentas utilizar literales, por ejemplo:
$stmt = $conProyecto->stmt_init();
$stmt->prepare('SELECT producto, unidades FROM stocks WHERE unidades<2');
$stmt->execute();
$stmt->bind_result($producto, $unidades);
while($stmt->fetch()) {
echo "<p>Producto $producto: $unidades unidades.</p>";
}
$stmt->close();
$conProyecto->close();
Debes conocer
En el manual de PHP tienes más información sobre consultas preparadas y la
clase mysqli_stmt.
Recomendación
Tanto $stmt->prepare() como $stmt->execute() devuelven un dato de tipo booleano,
podemos usar esto para controlar errores, fíjate en el ejemplo siguiente:
$stmt=$conProyecto->stmt_init();
$cod=1;
$consulta="select nombre from productos where id=?";
if(!($stmt->prepare($consulta))){
echo "Se ha producido un error: " . $conProyecto->error();
die();
}
$stmt->bind_param('i', $cod);
if(!$stmt->execute()){
//error
}
. . .
Ejercicio resuelto
A partir de la página web obtenida en el ejercicio anterior, añade la opción de
modificar el número de unidades del producto en cada una de las tiendas.
Utiliza una consulta preparada para la actualización de registros en la tabla
stocks. No es necesario tener en cuenta las tareas de inserción (no existían
unidades anteriormente) y borrado (si el número final de unidades es cero).
Mostrar retroalimentación
El objetivo es que si llegado el momento necesitas cambiar el servidor de base de datos, las
modificaciones que debas realizar en tu código sean mínimas. Incluso es posible desarrollar
aplicaciones preparadas para utilizar un almacenamiento u otro según se indique en el
momento de la ejecución, pero éste no es el objetivo principal de PDO. PDO no abstrae de
forma completa el sistema gestor que se utiliza. Por ejemplo, no modifica las
sentencias SQL para adaptarlas a las características específicas de cada servidor. Si esto
fuera necesario, habría que programar una capa de abstracción completa.
La extensión PDO debe utilizar un driver o controlador específico para el tipo de base de
datos que se utilice. Para consultar los controladores disponibles en tu instalación de PHP,
puedes utilizar la información que proporciona la función phpinfo().
PDO se basa en las características de orientación a objetos de PHP pero, al contrario que
la extensión MySQLi, no ofrece un interface de programación dual. Para acceder a las
funcionalidades de la extensión tienes que emplear los objetos que ofrece, con sus métodos
y propiedades. No existen funciones alternativas.
Por ejemplo, podemos establecer una conexión con la base de datos 'proyecto' creada
anteriormente de la siguiente forma:
$host = "localhost";
$db = "proyecto";
$user = "gestor";
$pass = "secreto";
$dsn = "mysql:host=$host;dbname=$db";
$conProyecto=new PDO($dsn, $user, $pass);
//se recomienda guardar los datos(host, user...) en variables porque si estos cambian
//solo tenemos que actualizar el valor de estas variables
Si quisieras indicar al servidor MySQL que utilice codificación UTF-8 o UTF8mb4 (utf8 con
soporte para "emojis" muy recomendable) para los datos que se transmitan, aunque hay
más formas de hacerlo la siguiente es la más sencilla.
$dsn = "mysql:host=$host;dbname=$db;charset=utf8mb4";
Manual de PHP.
Una vez establecida la conexión, puedes utilizar el método getAttribute para obtener
información del estado de la conexión y setAttribute para modificar algunos parámetros que
afectan a la misma. Por ejemplo, para obtener la versión del servidor puedes hacer:
$version = $conProyecto->getAttribute(PDO::ATTR_SERVER_VERSION);
echo "Versión: $version";
Y si quieres por ejemplo que te devuelva todos los nombres de columnas en mayúsculas:
Y muy importante para controlar los errores tendremos el atributo: ATTR_ERRMODE con los
posible valores:
$conProyecto->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Para cerrar la conexión hay que saber que la misma permanecerá activa durante el tiempo
de vida del objeto PDO. Para cerrarla, es necesario destruir el objeto asegurándose de que
todas las referencias a él existentes sean eliminadas; esto se puede hacer asignando null a
la variable que contiene el objeto.
$conProyecto = null;
Manual getAttribute.
Manual setAttribute.
Autoevaluación
Para establecer una conexión con MySQL utilizando PDO, ¿dónde se
puede indicar el número de puerto TCP?
Solución
1. Opción correcta
2. Incorrecto
3.2.2.- Ejecución de consultas.
Para ejecutar una consulta SQL utilizando PDO, debes diferenciar
aquellas sentencias SQL que no devuelven como resultado un
conjunto de datos, de aquellas otras que sí lo devuelven.
$host = "localhost";
$db = "proyecto";
$user = "gestor";
$pass = "secreto";
$dsn = "mysql:host=$host;dbname=$db";
$conProyecto=new PDO($dsn, $user, $pass);
$conProyecto->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$resultado = $conProyecto->query("SELECT producto, unidades FROM stock");
Por defecto PDO trabaja en modo "autocommit", esto es, confirma de forma automática cada
sentencia que ejecuta el servidor. Para trabajar con transacciones, PDO incorpora tres
métodos:
beginTransaction.
Deshabilita el modo "autocommit" y comienza una nueva transacción, que
finalizará cuando ejecutes uno de los dos métodos siguientes.
commit. Confirma la transacción actual.
rollback. Revierte los cambios llevados a cabo en la transacción actual.
$ok = true;
$conProyecto->beginTransaction();
if(!$conProyecto->exec('DELETE …')) $ok = false;
if(!$conProyecto->exec('UPDATE …')) $ok = false;
Ejercicio resuelto
De una forma similar al anterior ejercicio de transacciones, utiliza PDO para
repartir entre las tiendas las tres unidades que figuran en unidades, del
producto con código PAPYRE62GB, en la tienda de código 1. Es decir
primero con un update ponemos el número de unidades de unidades del
producto de nombre corto PAPYRE62GB a 1 en la tienda de código 1 y luego
hacemos un insert para insertar dos unidades de dicho producto en la tienda
de código 2.
Mostrar retroalimentación
Autoevaluación
Si programas tu aplicación correctamente utilizando "beginTransaction" antes
de realizar un cambio, ¿siempre será posible revertirlo utilizando
"rollback"?
Sí.
No.
1. Incorrecto
2. Opción correcta
3.2.3.- Obtención y utilización de
conjuntos de resultados.
Al igual que con la extensión MySQLi, en PDO tienes varias
posibilidades para tratar con el conjunto de resultados devuelto por el
método query. La más utilizada es el método fetch de la clase
PDOStatement. Este método devuelve un registro del conjunto de
resultados, o false si ya no quedan registros por recorrer.
. . .
Everaldo Coelho (GNU/GPL)
Por defecto, el método fetch genera y devuelve a partir de cada registro un array con claves
numéricas y asociativas. Para cambiar su comportamiento, admite un parámetro opcional
que puede tomar uno de los siguientes valores:
. . .
PDO::FETCH_LAZY. Devuelve tanto el objeto como el array con clave dual anterior.
PDO::FETCH_BOUND. Devuelve true y asigna los valores del registro a variables, según
se indique con el método bindColumn. Este método debe ser llamado una vez por cada
columna, indicando en cada llamada el número de columna (empezando en 1) y la
variable a asignar.
. . .
También podemos utilizar fecthAll() que te trae todos los datos de golpe, sin abrir ningún
puntero, almacenándolos en un array. Se recomienda cuando no se esperan demasiados
resultados, que podrían provocar problemas de memoria al querer guardar de golpe en un
array muchas filas provenientes de una consulta.
$resultado = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($resultado as $row){
echo $row["nombre"]." ".$row["apellido"];
}
Ejercicio resuelto
Modifica la página web que muestra las unidades de un producto en las
distintas tiendas, obtenida en un ejercicio anterior utilizando MySQLi, para
que use PDO.
Mostrar retroalimentación
Autoevaluación
¿Cuál es el comportamiento por defecto del método "fetch"?
Solución
1. Opción correcta
2. Incorrecto
3.2.4.- Consultas preparadas.
. . .
O también utilizando parámetros con nombre, precediéndolos por el símbolo de dos puntos.
Antes de ejecutar la consulta hay que asignar un valor a los parámetros utilizando el
método bindParam de la clase PDOStatement. Si utilizas signos de interrogación para marcar los
parámetros, el procedimiento es equivalente al método bindColumn que acabamos de ver.
$cod_producto = "TABLET";
$nombre_producto = "Tablet PC";
$consulta->bindParam(1, $cod_producto);
$consulta->bindParam(2, $nombre_producto);
Si utilizas parámetros con nombre, debes indicar ese nombre en la llamada a bindParam.
$consulta->bindParam(":cod", $cod_producto);
$consulta->bindParam(":nombre", $nombre_producto);
Tal y como sucedía con la extensión MySQLi, cuando uses bindParam para
asignar los parámetros de una consulta preparada, deberás usar siempre
variables como en el ejemplo anterior.
Una vez preparada la consulta y enlazados los parámetros con sus valores, se ejecuta la
consulta utilizando el método execute.
$stmt->execute();
También existe otra forma de pasar valores a los parámetros. Hay un método lazy, que
funciona pasando los valores mediante un array, al método execute().
$nombre="Monitores";
$codigo="MONI";
$stmt = $conProyecto->prepare('INSERT INTO familia (cod, nombre) VALUES (:cod, :nombre)');
$stmt->execute([ ':cod'=>$codigo, ':nombre'=>$nombre]);
]);
Ejercicio resuelto
Modifica el ejercicio sobre consultas preparadas que realizaste con la
extensión MySQLi, el que modificaba el número de unidades de un producto
en las distintas tiendas, para que utilice ahora la extensión PDO.
Mostrar retroalimentación
Caso práctico
En sus primeros pasos en la programación en
lenguaje PHP, Carlos se ha encontrado
frecuentemente con pequeños errores en el
código. En la mayoría de las ocasiones los
errores estaban causados por fallos de
programación. Pero en las recientes pruebas
llevadas a cabo con bases de datos, se ha dado
cuenta de que en algunas ocasiones se
pueden producir fallos ajenos al programa.
Debes conocer
La lista completa de constantes la puedes consultar en el manual de PHP,
donde también se describe el tipo de errores que representa.
error_reporting. Indica qué tipos de errores se notificarán. Su valor se forma utilizando los
operadores a nivel de bit para combinar las constantes anteriores. Su valor
predeterminado es E_ALL & ~E_NOTICE que indica que se notifiquen todos los errores
(E_ALL) salvo los avisos en tiempo de ejecución (E_NOTICE).
display_errors. En su valor por defecto (On), hace que los mensajes se envíen a la salida
estándar (y por lo tanto se muestren en el navegador). Se debe desactivar (Off) en los
servidores que no se usan para desarrollo sino para producción.
Desde código, puedes usar la función error_reporting con las constantes anteriores para
establecer el nivel de notificación en un momento determinado. Por ejemplo, si en algún
lugar de tu código figura una división en la que exista la posibilidad de que el divisor sea
cero, cuando esto ocurra obtendrás un mensaje de error en el navegador. Para evitarlo,
puedes desactivar la notificación de errores de nivel E_WARNING antes de la división y
restaurarla a su valor normal a continuación:
Al usar la función error_reporting solo controlas qué tipo de errores va a notificar PHP. A veces
puede ser suficiente, pero para obtener más control sobre el proceso existe también la
posibilidad de reemplazar la gestión de los mismos por la que tú definas. Es decir, puedes
programar una función para que sea la que se ejecuta cada vez que se produce un error. El
nombre de esa función se indica utilizando set_error_handler y debe tener como mínimo dos
parámetros obligatorios (el nivel del error y el mensaje descriptivo) y hasta otros tres
opcionales con información adicional sobre el error (el nombre del fichero en que se
produce, el número de línea, y un volcado del estado de las variables en ese momento).
set_error_handler("miGestorDeErrores");
$resultado = $dividendo / $divisor;
restore_error_handler();
function miGestorDeErrores($nivel, $mensaje)
{
switch($nivel) {
case E_WARNING:
echo "Error de tipo WARNING: $mensaje.<br />";
break;
default:
echo "Error de tipo no especificado: $mensaje.<br />";
}
}
Por ejemplo, para lanzar una excepción cuando se produce una división por cero podrías
hacer:
try {
if ($divisor == 0)
throw new Exception("División por cero.");
$resultado = $dividendo / $divisor;
}catch (Exception $e) {
echo "Se ha producido el siguiente error: ".$e->getMessage();
}
PHP ofrece una clase base Exception para utilizar como manejador (handler) de excepciones.
Para lanzar una excepción no es necesario indicar ningún parámetro, aunque de forma
opcional se puede pasar un mensaje de error (como en el ejemplo anterior) y también un
código de error.
Entre los métodos que puedes usar con los objetos de la clase Exception están:
Las funciones internas de PHP y muchas extensiones como MySQLi usan el sistema de
errores visto anteriormente. Solo las extensiones más modernas orientadas a objetos, como
es el caso de PDO, utilizan este modelo de excepciones. En este caso, lo más común es
que la extensión defina sus propios manejadores de errores heredando de la clase Exception
(veremos cómo utilizar la herencia en una unidad posterior).
Clase ErrorException.
Vimos en el apartado 3.2.1 que la clase PDO permitia definir la fórmula que usará cuando se
produzca un error, utilizando el atributo PDO::ATTR_ERRMODE. Las posibilidades eran:
PDO::ERRMODE_SILENT.
PDO::ERRMODE_WARNING.
PDO::ERRMODE_EXCEPTION. Cuando se produce un error lanza una excepción utilizando
el manejador propio PDOException.
Es decir, que si quieres utilizar excepciones con la extensión PDO, debes configurar la
conexión haciendo:
$conProyecto->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$host = "localhost";
$db = "proyecto";
$user = "gestor";
$pass = "1234";
$dsn = "mysql:host=$host;dbname=$db";
try {
$conProyecto = new PDO($dsn, $user, $pass);
$conProyecto->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $ex) {
die("Error en la conexion, mensaje de error: " . $ex->getMessage());
}
Captura la excepción que lanza PDO debido a que la contraseña era "secreto" y no "1234".
El bloque catch muestra el siguiente mensaje:
Error en la conexion, mensaje de error: SQLSTATE[HY000] [1045] Access denied for user 'gestor
Ejercicio resuelto
Agrega control de excepciones para controlar los posibles errores de conexión
que se puedan producir en el último ejercicio del apartado 3.2.4. Mostraremos
los posible errores abajo de la página.
Mostrar retroalimentación
Revisa en la solución propuesta los cambios realizados para utilizar
excepciones en el establecimiento de la conexión.
Autoevaluación
¿Cuántos bloques "catch" se han de utilizar después de un bloque "try"?
Uno.
Uno o más.
Incorrecto, siempre debe haber al menos uno, pero ¿puede haber más?
Efectivamente, debe haber como mínimo uno, pero puede haber más. En
este caso se ejecutará el bloque "catch" cuyo manejador coincida que se
ha lanzado mediante "throw". Esto sólo tiene sentido si se utilizan distintos
manejadores extendiendo el base que incluye PHP, "Exception".
Solución
1. Incorrecto
2. Opción correcta