# Resumen DSW UT4 (BD) **PHP soporta más de 15 sistemas gestores de bases de datos**: SQLite, Oracle, SQL Server, PostgreSQL, IBM DB2, MySQL, etc. Hasta la versión 5 de PHP, el acceso a las bases de datos se hacía principalmente utilizando extensiones específicas para cada sistema gestor de base de datos (extensiones nativas). Es decir, que si queríamos acceder a una base de datos de PostgreSQL, deberíamos instalar y utilizar la extensión de ese gestor en concreto. Las funciones y objetos a utilizar eran distintos para cada extensión. **A partir de la versión 5** de PHP se introdujo en el lenguaje una extensión para acceder de una forma común a distintos sistemas gestores: **PDO**. La gran ventaja de PDO está clara: Podemos seguir **utilizando una misma sintaxis aunque cambiemos el motor de nuestra base de datos**. **Por el contrario**, en algunas ocasiones preferiremos seguir usando extensiones nativas en nuestros programas. Mientras PDO ofrece un conjunto común de funciones, **las extensiones nativas normalmente ofrecen más potencia** (acceso a funciones específicas de cada gestor de base de datos) y en algunos casos **también mayor velocidad**. ## MySQL MySQL es un sistema gestor de bases de datos (SGBD) relacionales. Es un programa de código abierto. ## MySQLi Esta extensión se desarrolló para aprovechar las ventajas que ofrecen las versiones 4.1.3 y posteriores de MySQL, y viene incluida con PHP a partir de la versión 5. Para establecer una conexión con un servidor MySQL y consultar su versión, podemos utilizar cualquiera de las siguientes formas: ```php= // Utilizando constructores y métodos de la programación orientada a objetos $conexion = new mysqli('servidor', 'usuario', 'contraseña', 'base_de_datos'); print conexion->server_info; // Utilizando llamadas a funciones $conexion = mysqli_connect('servidor', 'usuario', 'contraseña', 'base_de_datos'); print mysqli_get_server_info($conexion); ``` Es importante verificar que la conexión se ha establecido correctamente. Para comprobar el error, en caso de que se produzca, puedes usar las siguientes propiedades de la clase mysqli: - **connect_errno** (o la función mysqli_connect_errno) devuelve el número de error o null si no se produce ningún error. - **connect_error** (o la función mysqli_connect_error) devuelve el mensaje de error o null si no se produce ningún error. ```php= $conProyecto = new mysqli('localhost', 'gestor', 'secreto', 'proyecto'); $error = $conProyecto->connect_errno; if ($error != null) { echo "<p>Error $error conectando a la base de datos: $conProyecto->connect_error</p>"; die(); } ``` Si una vez establecida la conexión, queremos cambiar la base de datos podemos usar el método "select_db" (o la función "mysqli_select_db" de forma equivalente) para indicar el nombre de la nueva. ```php= / Utilizando el método connect $conProyecto->select_db('otra_bd'); ``` Una vez finalizadas las tareas con la base de datos, utilizaremos 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. ```php= $conProyecto->close(); ``` Entre las mejoras que aporta respecto a la **antigua extensión mysql**, figuran: - Interface orientado a objetos. - Soporte para transacciones. - Soporte para consultas preparadas. - Mejores opciones de depuración y seguridad. Entre las opciones que puedes configurar para la extensión MySQLi están: - **mysqli.allow_persistent**. Permite crear conexiones persistentes. - **mysqli.default_port**. Número de puerto TCP predeterminado a utilizar cuando se conecta al servidor de base de datos. - **mysqli.reconnect**. Indica si se debe volver a conectar automáticamente en caso de que se pierda la conexión. - **mysqli.default_host**. Host predeterminado a usar cuando se conecta al servidor de base de datos. - **mysqli.default_user**. Nombre de usuario predeterminado a usar cuando se conecta al servidor de base de datos. - **mysqli.default_pw**. Contraseña predeterminada a usar cuando se conecta al servidor de base de datos. ### Ejecución de consultas La forma más inmediata de ejecutar una consulta, es el método **query**, equivalente a la función **mysqli_query**. 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. Si se ejecuta una consulta de acción que no devuelve datos (como una sentencia SQL de tipo UPDATE, INSERT o DELETE), la llamada devuelve true si se ejecuta correctamente o false en caso contrario. El **número de registros afectados se puede obtener con la propiedad affected_rows** (o con la función mysqli_affected_rows). ```php= $conProyecto = new mysqli('localhost', 'gestor', 'secreto', 'proyecto'); $error = $conProyecto->connect_errno; if ($error == null) { $resultado = $conProyecto->query('DELETE FROM stock WHERE unidades='); if ($resultado) { echo "<p>Se han borrado $conProyecto->affected_rows registros.</p>"; } $conProyecto->close(); // Cerramos la conexion } ``` En el caso de ejecutar una sentencia SQL que sí devuelva datos (como un SELECT), éstos se devuelven en forma de un objeto resultado. Para trabajar con los datos obtenidos del servidor, tenemos varias posibilidades: - **fetch_array** (función mysqli_fetch_array). Obtiene un registro completo del conjunto de resultados y lo almacena en un array. ```php= $resultado = $conProyecto->query('SELECT producto, unidades FROM stocks WHERE unidades < 2'); $stock = $resultado->fetch_array(); // Obtenemos el primer registro $producto = $stock['producto']; // O también $stock[0]; $unidades = $stock['unidades']; // O también $stock[1]; echo "<p>Producto $producto: $unidades unidades.</p>"; ``` Este comportamiento por defecto se puede modificar utilizando un parámetro opcional, que puede tomar los siguientes valores: 1. **MYSQLI_NUM**. Devuelve un array con claves numéricas. 2. **MYSQLI_ASSOC**. Devuelve un array asociativo. 3. **MYSQLI_BOTH**. Es el comportamiento por defecto, en el que devuelve un array con claves numéricas y asociativas. - **fetch_assoc** (función mysqli_fetch_assoc). Idéntico a fetch_array pasando como parámetro MYSQLI_ASSOC. - **fetch_row** (función mysqli_fetch_row). Idéntico a fetch_array pasando como parámetro MYSQLI_NUM. - **fetch_object** (función mysqli_fetch_object). Similar a los métodos anteriores, pero devuelve un objeto en lugar de un array. Las propiedades del objeto devuelto se corresponden con cada uno de los campos del registro. 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): ```php= $resultado->free(); ``` ### Transacciones En la parte de Preguntas ### Consultas preparadas Existe un riesgo de seguridad muy importante al usar formularios para tratar datos en una base de datos, la **"inyección SQL"**. Uno 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, debemos utilizar la clase **mysqli_stmt**. ```php= $conProyecto = new mysqli('localhost', 'gestor', 'secreto', 'proyecto'); $stmt = $conProyecto->stmt_init(); ``` Los pasos que debemos seguir para ejecutar una consulta preparada son: - Preparar la consulta en el servidor MySQL utilizando el método prepare (función mysqli_stmt_prepare). - Ejecutar la consulta, tantas veces como sea necesario, con el método execute (función mysqli_stmt_execute). - Una vez que ya no se necesita más, se debe ejecutar el método close (función mysqli_stmt_close). ```php= $stmt = $conProyecto->stmt_init(); $stmt->prepare('INSERT INTO familias (cod, nombre) VALUES ("TABLET", "Tablet PC")'); $stmt->execute(); $stmt->close(); $conProyecto->close(); ``` 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 debemos indicar con un signo de interrogación su posición dentro de la sentencia SQL. Y antes de ejecutar la consulta tenemos que utilizar el método **bind_param** (o la función **mysqli_stmt_bind_param**) para sustituir cada parámetro por su valor. El primer parámetro del método bind_param es una cadena de texto en la que cada carácter indica el tipo de un parámetro, según la siguiente tabla. | Carácter | Tipo del parámetro | | -------- | -------- | | i | Número entero | | d | Número real | | s | Cadena de texto | | b | Contenido en binario | ```php= $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(); ``` ## PDO ### Establecimiento de conexiones Para establecer una conexión con una base de datos utilizando PDO, debemos instanciar un objeto de la clase PDO pasándole los siguientes parámetros (solo el primero es obligatorio): - **Origen de datos** (DSN). Es una cadena de texto que indica qué controlador se va a utilizar y, a continuación, separadas por el carácter dos puntos, los parámetros específicos necesarios por el controlador, como por ejemplo el nombre o dirección IP del servidor y el nombre de la base de datos. - **Nombre de usuario** con permisos para establecer la conexión. - **Contraseña** del usuario. - **Opciones de conexión**, almacenadas en forma de array. Los parámetros específicos para utilizar en la cadena DSN (separadas unas de otras por el carácter punto y coma) a continuación del prefijo mysql: son los siguientes: - **host**. Nombre o dirección IP del servidor. - **port**. Número de puerto TCP en el que escucha el servidor. - **dbname**. Nombre de la base de datos. - **unix_socket**. Socket de MySQL en sistemas Unix. ```php= $host = "localhost"; $db = "proyecto"; $user = "gestor"; $pass = "secreto"; $dsn = "mysql:host=$host;dbname=$db"; $conProyecto=new PDO($dsn, $user, $pass); ``` Si quisieramos indicar al servidor MySQL que utilice codificación UTF-8 o UTF8mb4 para los datos que se transmitan, aunque hay más formas de hacerlo la siguiente es la más sencilla. ```php= $dsn = "mysql:host=$host;dbname=$db;charset=utf8mb4"; ``` Una vez establecida la conexión, podemos 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 podemos hacer: ```php= $version = $conProyecto->getAttribute(PDO::ATTR_SERVER_VERSION); echo "Versión: $version"; ``` Y si queremos, por ejemplo, que devuelva todos **los nombres de columnas en mayúsculas**: ```php= $version = $conProyecto->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); ``` Para controlar los errores tendremos el atributo: ATTR_ERRMODE con los posible valores: - **ERRMODE_SILENT**: El modo por defecto, no muestra errores. - **ERRMODE_WARNING**: Además de establecer el código de error, emitirá un mensaje. - **E_WARNING**: Modo empleado para depurar o hacer pruebas para ver errores sin interrumpir el flujo de la aplicación. - **ERRMODE_EXCEPTION**: Además de establecer el código de error, lanzará una PDOException que podemos capturar en un bloque try catch(). ```php= $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. ```php= $conProyecto = null; ``` ### Ejecución de consultas Para ejecutar una consulta SQL utilizando PDO, debemos diferenciar aquellas sentencias SQL que no devuelven como resultado un conjunto de datos, de aquellas otras que sí lo devuelven. En el caso de las consultas de acción, como INSERT, DELETE o UPDATE, el **método exec devuelve el número de registros afectados**. ```php= $registros = $conProyecto->exec('DELETE FROM stocks WHERE unidades=0'); echo "<p>Se han borrado $registros registros.</p>"; ``` Si la consulta **genera un conjunto de datos**, como es el caso de SELECT, debemos utilizar el **método query**, que devuelve un objeto de la clase PDOStatement. ```php= $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", 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. Una vez ejecutado un commit o un rollback, se volverá al modo de confirmación automática. ```php= $ok = true; $conProyecto->beginTransaction(); if(!$conProyecto->exec('DELETE …')) $ok = false; if(!$conProyecto->exec('UPDATE …')) $ok = false; … if ($ok) $conProyecto->commit(); // Si todo fue bien confirma los cambios else $dwes->rollback(); // y si no, los revierte ``` ### Obtención y utilización de conjuntos de resultados 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. ```php= $conProyecto = new PDO(". . ."); $resultado = $conProyecto->query("SELECT producto, unidades FROM stocks"); while ($registro = $resultado->fetch()) { echo "Producto ".$registro['producto'].": ".$registro['unidades']."; } ``` 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_ASSOC**. Devuelve solo un array asociativo. - **PDO::FETCH_NUM**. Devuelve solo un array con claves numéricas. - **PDO::FETCH_BOTH**. Devuelve un array con claves numéricas y asociativas. Es el comportamiento por defecto. - **PDO::FETCH_OBJ**. Devuelve un objeto cuyas propiedades se corresponden con los campos del registro. - **PDO::FETCH_LAZY**. Devuelve tanto el objeto como el array con clave dual anterior. ```php= . . . $conProyecto = new PDO(". . ."); $resultado = $conProyecto->query("SELECT producto, unidades FROM stocks"); while ($registro = $resultado->fetch(PDO::FETCH_OBJ)) { echo "Producto ".$registro->producto.": ".$registro->unidades."<br/>"; } ``` - **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. ```php= . . . $conProyecto = new PDO(". . ."); $resultado = $conProyecto->query("SELECT producto, unidades FROM stocks"); $resultado->bindColumn(1, $producto); $resultado->bindColumn(2, $unidades); while ($registro = $resultado->fetch(PDO::FETCH_BOUND)) { echo "Producto ".$producto.": ".$unidades."<br />"; } ``` 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. ```php= $resultado = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($resultado as $row){ echo $row["nombre"]." ".$row["apellido"]; } ``` ### Consultas preparadas Para preparar la consulta en el servidor MySQL, debemos utilizar el método **prepare** de la clase PDO. Este método devuelve un objeto de la clase PDOStatement. Los parámetros se pueden marcar utilizando **signos de interrogación** como en el caso anterior. ```php= . . . $conProyecto = new PDO(". . ."); $stmt = $conProyecto->prepare('INSERT INTO familia (cod, nombre) VALUES (?, ?)'); ``` **O también utilizando parámetros con nombre**, precediéndolos por el símbolo de dos puntos. ```php= $stmt = $conProyecto->prepare('INSERT INTO familia (cod, nombre) VALUES (:cod, :nombre)'); ``` **Antes de ejecutar la consulta** hay que asignar un valor a los parámetros utilizando el método **bindParam** de la clase PDOStatement. **Si utilizamos signos de interrogación** para marcar los parámetros, el procedimiento es **equivalente al método bindColumn** que acabamos de ver. ```php= $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. ```php= $consulta->bindParam(":cod", $cod_producto); $consulta->bindParam(":nombre", $nombre_producto); ``` Una vez preparada la consulta y enlazados los parámetros con sus valores, se ejecuta la consulta utilizando el método execute. ```php= $stmt->execute(); ``` También existe otra forma de pasar valores a los parámetros. Hay un método, que funciona pasando los valores mediante un array, al método execute() ```php= $nombre="Monitores"; $codigo="MONI"; $stmt = $conProyecto->prepare('INSERT INTO familia (cod, nombre) VALUES (:cod, :nombre)'); $stmt->execute([ ':cod'=>$codigo, ':nombre'=>$nombre]); ``` ## Preguntas 1. ¿A qué hacen referencia las siglas PDO? - [ ] A un motor de almacenamiento utilizado por MariaDB o MySQL. - [x] A una extensión de PHP que permite acceder a varios gestores de bases de datos. 2. Relaciona cada herramienta de administración con el tipo de interface que utiliza: MySQL Workbench 1.- Línea de comandos mysql 2.- Web phpMyAdmin 3.- Nativo mysqladmin 4.- Línea de comandos 3. 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. - [x] mysql. 4. ¿Qué interface o interfaces de programación admite la extensión MySQLi? - [ ] Orientado a objetos únicamente - [x] Dos interfaces de programación: procedimental y orientado a objetos. 5. 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. - [x] MYSQLI_USE_RESULT. 6. 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? - [x] Si - [ ] No 7. Para establecer una conexión con MySQL utilizando PDO, ¿dónde se puede indicar el número de puerto TCP? - [x] En la cadena DSN que indica el origen de datos. - [ ] En el array en que figuran las opciones específicas de conexión con el servidor. 8. Si programas tu aplicación correctamente utilizando "beginTransaction" antes de realizar un cambio, ¿siempre será posible revertirlo utilizando "rollback"? - [ ] Sí. - [x] No. 9. ¿Cuál es el comportamiento por defecto del método "fetch"? - [x] Devuelve un array con claves numéricas y asociativas. - [ ] Devuelve un array asociativo. 10. ¿Cuántos bloques "catch" se han de utilizar después de un bloque "try"? - [ ] Uno. - [x] Uno o más. ###### tags: `DAW` `DSW` `PHP` `UT4`