Diferencia entre revisiones de «PHP UD3 EXERCICIOS PROPOSTOS»
(→Resumo) |
|||
| (No se muestran 4 ediciones intermedias del mismo usuario) | |||
| Línea 1: | Línea 1: | ||
== Introdución == | == Introdución == | ||
| − | Nesta unidade propoñemos varios exercicios completos de mantemento de datos empregando PHP 8.5 e | + | Nesta unidade propoñemos varios exercicios completos de mantemento de datos empregando PHP 8.5 e un servidor MySQL 8.0 ou 8.4. |
O obxectivo é practicar as operacións fundamentais dun CRUD: | O obxectivo é practicar as operacións fundamentais dun CRUD: | ||
| Línea 12: | Línea 12: | ||
Nos exemplos completos empregaremos '''PDO (PHP Data Objects)''' como mecanismo principal de acceso á base de datos. | Nos exemplos completos empregaremos '''PDO (PHP Data Objects)''' como mecanismo principal de acceso á base de datos. | ||
| − | Ao final inclúese unha sección con <code>mysqli</code> | + | Ao final inclúese unha sección comparativa con <code>mysqli</code>. Nos exercicios e prácticas deste módulo empregarase '''PDO''' salvo indicación expresa en contrario, seguindo os [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql apuntes de operacións contra MySQL]. |
<br /> | <br /> | ||
| Línea 74: | Línea 74: | ||
/var/www/confBD/bd.php | /var/www/confBD/bd.php | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | * '''PASOS A SEGUIR E CONSIDERACIÓNS:''' | ||
| + | :* Importar a base de datos <code>CIRCO</code> en MySQL 8 e comprobar a estrutura e as restricións das táboas. | ||
| + | :* Crear un usuario específico con permisos mínimos (<code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code> e <code>DELETE</code>) sobre as táboas necesarias. Non empregar <code>root</code> na aplicación. | ||
| + | :* Gardar as credenciais e as constantes SQL en <code>/var/www/confBD/bd.php</code>, fóra do directorio público do sitio web. | ||
| + | :* Durante o desenvolvemento consultar os erros no rexistro do servidor e empregar un depurador cando sexa posible. A opción <code>display_errors</code> non debe estar activa en produción. | ||
| + | :* Configurar os permisos de forma que o usuario do servidor web poida ler os scripts e o arquivo de configuración, sen conceder escritura onde non sexa necesaria. Evitar que outros usuarios locais poidan ler as credenciais. | ||
| + | :* Manter separados os formularios HTML e os scripts de procesamento de <code>INSERT</code>, <code>UPDATE</code> e <code>DELETE</code>. | ||
| + | :* As consultas preparadas non deben reconstruírse concatenando datos do formulario; os valores variables enlázanse con parámetros. | ||
<br /> | <br /> | ||
| Línea 86: | Línea 96: | ||
<?php | <?php | ||
| + | // Arquivo de configuración fóra do directorio público. | ||
define('SERVIDOR', 'localhost'); | define('SERVIDOR', 'localhost'); | ||
define('USUARIO', 'user_php'); | define('USUARIO', 'user_php'); | ||
| Línea 119: | Línea 130: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| Línea 290: | Línea 302: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| Línea 449: | Línea 462: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| − | if (!isset($_POST['btnAltaPista']) || $_POST['btnAltaPista'] !== 'ALTA') { | + | if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnAltaPista']) || $_POST['btnAltaPista'] !== 'ALTA') { |
throw new RuntimeException('Operación non válida.'); | throw new RuntimeException('Operación non válida.'); | ||
} | } | ||
| Línea 534: | Línea 548: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| Línea 616: | Línea 631: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| − | if (!isset($_POST['btnModificarPista']) || $_POST['btnModificarPista'] !== 'MODIFICAR') { | + | if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnModificarPista']) || $_POST['btnModificarPista'] !== 'MODIFICAR') { |
throw new RuntimeException('Operación non válida.'); | throw new RuntimeException('Operación non válida.'); | ||
} | } | ||
| Línea 657: | Línea 673: | ||
$mensaxe = $stmt->rowCount() === 1 | $mensaxe = $stmt->rowCount() === 1 | ||
? 'Pista actualizada correctamente.' | ? 'Pista actualizada correctamente.' | ||
| − | : ' | + | : 'Non se modificou ningunha fila (pode non existir ou non haber cambios).'; |
header('Location: consultar_pistas.php?msg=' . urlencode($mensaxe)); | header('Location: consultar_pistas.php?msg=' . urlencode($mensaxe)); | ||
| Línea 705: | Línea 721: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| − | if (!isset($_POST['btnBorrarPista']) || $_POST['btnBorrarPista'] !== 'BORRAR') { | + | if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnBorrarPista']) || $_POST['btnBorrarPista'] !== 'BORRAR') { |
throw new RuntimeException('Operación non válida.'); | throw new RuntimeException('Operación non válida.'); | ||
} | } | ||
| Línea 812: | Línea 829: | ||
=== Valores NULL con PDO === | === Valores NULL con PDO === | ||
| − | + | Con PDO en PHP representaremos a ausencia de valor mediante <code>null</code> e enlazaremos explicitamente o parámetro como <code>PDO::PARAM_NULL</code> cando sexa necesario: | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | Con PDO | ||
| − | |||
| − | |||
::<syntaxhighlight lang="php" enclose="div"> | ::<syntaxhighlight lang="php" enclose="div"> | ||
| Línea 956: | Línea 955: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| Línea 1199: | Línea 1199: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| Línea 1303: | Línea 1304: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| − | if (!isset($_POST['btnAltaAnimal']) || $_POST['btnAltaAnimal'] !== 'ALTA') { | + | if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnAltaAnimal']) || $_POST['btnAltaAnimal'] !== 'ALTA') { |
throw new RuntimeException('Operación non válida.'); | throw new RuntimeException('Operación non válida.'); | ||
} | } | ||
| Línea 1456: | Línea 1458: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| Línea 1551: | Línea 1554: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| − | if (!isset($_POST['btnModificarAnimal']) || $_POST['btnModificarAnimal'] !== 'MODIFICAR') { | + | if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnModificarAnimal']) || $_POST['btnModificarAnimal'] !== 'MODIFICAR') { |
throw new RuntimeException('Operación non válida.'); | throw new RuntimeException('Operación non válida.'); | ||
} | } | ||
| Línea 1657: | Línea 1661: | ||
$mensaxe = $stmt->rowCount() === 1 | $mensaxe = $stmt->rowCount() === 1 | ||
? 'Animal actualizado correctamente.' | ? 'Animal actualizado correctamente.' | ||
| − | : ' | + | : 'Non se modificou ningunha fila (pode non existir ou non haber cambios).'; |
header('Location: consultar_animais.php?msg=' . urlencode($mensaxe)); | header('Location: consultar_animais.php?msg=' . urlencode($mensaxe)); | ||
| Línea 1709: | Línea 1713: | ||
[ | [ | ||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, | ||
| − | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| + | PDO::ATTR_EMULATE_PREPARES => false | ||
] | ] | ||
); | ); | ||
| − | if (!isset($_POST['btnBorrarAnimal']) || $_POST['btnBorrarAnimal'] !== 'BORRAR') { | + | if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnBorrarAnimal']) || $_POST['btnBorrarAnimal'] !== 'BORRAR') { |
throw new RuntimeException('Operación non válida.'); | throw new RuntimeException('Operación non válida.'); | ||
} | } | ||
| Línea 1987: | Línea 1992: | ||
* <code>mysqli</code> queda como ampliación, tamén empregando consultas preparadas. | * <code>mysqli</code> queda como ampliación, tamén empregando consultas preparadas. | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | == Comprobación final dos exercicios == | |
| + | Antes de dar por rematada a práctica comprobaremos que: | ||
| + | # As listaxes funcionan sen parámetros e as consultas dunha fila empregan <code>prepare()</code> e <code>execute()</code>. | ||
| + | # O alta valida os campos no servidor, insire mediante <code>POST</code> e trata as claves duplicadas. | ||
| + | # A modificación conserva a clave primaria orixinal e permite cambiar o nome da pista ou do animal. | ||
| + | # O borrado só se realiza mediante <code>POST</code> e trata posibles conflitos de integridade referencial. | ||
| + | # Os campos opcionais baleiros gárdanse como <code>NULL</code> cando corresponde e non como texto <code>'null'</code>. | ||
| + | # As etiquetas, textos e atributos HTML amosan os datos mediante <code>htmlspecialchars()</code>. | ||
| + | # Non se mostran credenciais nin mensaxes técnicas de excepción no navegador. | ||
| + | # As probas inclúen entradas mal formadas, valores límite e rexistros inexistentes. | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
<br /> | <br /> | ||
| − | + | == Ampliación dos exercicios == | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | * Realizar unha segunda versión dos exercicios anteriores nas páxinas de 'procesar....' para que se chame a un procedemento almacenado de Mysql. | |
| + | : Define a chamada ao procedemento o os parámetros necesarios no arquivo bd.php. | ||
Revisión actual del 10:33 8 oct 2026
Sumario
- 1 Introdución
- 2 Para todos os exercicios
- 3 Exercicio 1: mantemento da táboa PISTAS
- 3.1 Consultas necesarias
- 3.2 Arquivo pistas/consultar_pistas.php
- 3.3 Arquivo pistas/campos_pista.php
- 3.4 Arquivo pistas/alta_pista.php
- 3.5 Arquivo pistas/procesar_alta_pista.php
- 3.6 Arquivo pistas/editar_pista.php
- 3.7 Arquivo pistas/procesar_editar_pista.php
- 3.8 Arquivo pistas/procesar_borrar_pista.php
- 4 Exercicio 2: mantemento da táboa ANIMALES
- 4.1 Valores NULL con PDO
- 4.2 Consultas necesarias
- 4.3 Arquivo animais/consultar_animais.php
- 4.4 Carga das pistas e atraccións dos formularios
- 4.5 Arquivo animais/campos_animal.php
- 4.6 Arquivo animais/alta_animal.php
- 4.7 Validación dos datos opcionais
- 4.8 Arquivo animais/procesar_alta_animal.php
- 4.9 Arquivo animais/editar_animal.php
- 4.10 Arquivo animais/procesar_editar_animal.php
- 4.11 Arquivo animais/procesar_borrar_animal.php
- 5 mysqli como ampliación
- 6 Exercicios propostos
- 7 Resumo
- 8 Comprobación final dos exercicios
- 9 Ampliación dos exercicios
Introdución
Nesta unidade propoñemos varios exercicios completos de mantemento de datos empregando PHP 8.5 e un servidor MySQL 8.0 ou 8.4.
O obxectivo é practicar as operacións fundamentais dun CRUD:
- Create →
INSERT. - Read →
SELECT. - Update →
UPDATE. - Delete →
DELETE.
- Create →
Nos exemplos completos empregaremos PDO (PHP Data Objects) como mecanismo principal de acceso á base de datos.
Ao final inclúese unha sección comparativa con mysqli. Nos exercicios e prácticas deste módulo empregarase PDO salvo indicación expresa en contrario, seguindo os apuntes de operacións contra MySQL.
- NOTA IMPORTANTE: cando empregamos datos procedentes dun formulario debemos:
- validar os datos recibidos no servidor;
- empregar consultas preparadas cando unha orde SQL conteña datos variables;
- escapar os datos no momento de xerar a saída HTML;
- non mostrar ao usuario os detalles técnicos das excepcións;
- empregar
POSTpara as operacións que modifican datos; - comprobar os permisos ou autorización do usuario nunha aplicación real.
A protección fronte a ataques CSRF (Cross-Site Request Forgery) incorporarase cando se estuden as variables de sesións. Empregar POST non evita por si só un ataque CSRF.
Consideracións sobre os exercicios
- Todos os exercicios están baseados na base de datos CIRCO empregada noutro módulo.
- Ficheiro SQL: Archivo:CIRCO.zip
- A base de datos ten algunhas características pouco habituais nunha aplicación real porque está deseñada tamén para practicar SQL.
Por exemplo, nalgunhas táboas as claves primarias son cadeas visibles para o usuario:
PISTAS.nombre ANIMALES.nombre
Isto implica que, nunha modificación, o usuario pode cambiar o valor da clave primaria.
Por tanto teremos que conservar:
- o valor orixinal da clave primaria, para localizar a fila;
- o novo valor introducido polo usuario, para actualizar a fila.
Nunha aplicación habitual é frecuente empregar como clave primaria un identificador numérico autonumérico que o usuario non modifica.
- Se o usuario modifica unha clave primaria e o novo valor xa existe, a base de datos producirá un erro de clave duplicada.
Podemos tratar esta situación comprobando previamente se o novo valor existe ou deixando que a restrición da base de datos detecte o conflito e capturando a excepción. A restrición da base de datos debe existir igualmente.
- O usuario empregado pola aplicación debe ter unicamente os permisos necesarios. Para estes exercicios serán normalmente
SELECT,INSERT,UPDATEeDELETE. O permisoEXECUTEsó será necesario se tamén se practican procedementos almacenados.
- Os arquivos PHP estarán dentro da estrutura do sitio web, pero o arquivo coas credenciais e consultas estará fóra de
/var/www/html.
Empregaremos:
/var/www/confBD/bd.php
- PASOS A SEGUIR E CONSIDERACIÓNS:
- Importar a base de datos
CIRCOen MySQL 8 e comprobar a estrutura e as restricións das táboas. - Crear un usuario específico con permisos mínimos (
SELECT,INSERT,UPDATEeDELETE) sobre as táboas necesarias. Non empregarrootna aplicación. - Gardar as credenciais e as constantes SQL en
/var/www/confBD/bd.php, fóra do directorio público do sitio web. - Durante o desenvolvemento consultar os erros no rexistro do servidor e empregar un depurador cando sexa posible. A opción
display_errorsnon debe estar activa en produción. - Configurar os permisos de forma que o usuario do servidor web poida ler os scripts e o arquivo de configuración, sen conceder escritura onde non sexa necesaria. Evitar que outros usuarios locais poidan ler as credenciais.
- Manter separados os formularios HTML e os scripts de procesamento de
INSERT,UPDATEeDELETE. - As consultas preparadas non deben reconstruírse concatenando datos do formulario; os valores variables enlázanse con parámetros.
- Importar a base de datos
Para todos os exercicios
Arquivo /var/www/confBD/bd.php
O arquivo terá inicialmente:
<?php // Arquivo de configuración fóra do directorio público. define('SERVIDOR', 'localhost'); define('USUARIO', 'user_php'); define('PASSWORD', 'user_php'); define('BD', 'CIRCO');
Neste mesmo arquivo iremos engadindo as constantes coas consultas necesarias para os diferentes exercicios.
Esquema común para acceder á base de datos
Nas páxinas PHP que accedan á base de datos empregaremos o seguinte esquema:
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); // Operacións coa base de datos... } catch (PDOException $e) { error_log($e->getMessage()); die('Erro relacionado coa base de datos.'); } catch (Throwable $e) { error_log($e->getMessage()); die('Produciuse un erro na aplicación.'); }
Consultas con e sen parámetros
Se unha consulta non ten valores variables, podemos utilizar directamente query():
$stmt = $conex->query(CONSULTAR_PISTAS); $pistas = $stmt->fetchAll();
Se a consulta contén valores variables, empregaremos prepare() e execute():
$stmt = $conex->prepare( CONSULTAR_PISTA_POR_NOME ); $stmt->execute([ ':nombre' => $nombre ]); $pista = $stmt->fetch();
Non construiremos as consultas inserindo directamente os datos mediante sprintf(), concatenación ou interpolación.
Saída HTML
Os datos gardaranse normalmente na base de datos na súa forma orixinal.
Aplicaremos htmlspecialchars() cando os introduzamos nun documento HTML:
<?= htmlspecialchars( $pista['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ?>
Para un valor que pode ser NULL podemos facer:
<?= htmlspecialchars( (string) ($animal['nombre_pista'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ) ?>
Deste xeito NULL represéntase como unha cadea baleira na saída HTML.
Exercicio 1: mantemento da táboa PISTAS
Partindo da táboa PISTAS, crea unha aplicación que permita listar, dar de alta, modificar e borrar pistas.
Crearemos o cartafol:
pistas/
coas seguintes páxinas:
consultar_pistas.php alta_pista.php campos_pista.php procesar_alta_pista.php editar_pista.php procesar_editar_pista.php procesar_borrar_pista.php
- Xestión da táboa PISTAS
Consultas necesarias
Engadimos ao arquivo /var/www/confBD/bd.php:
define( 'CONSULTAR_PISTAS', 'SELECT nombre, aforo FROM PISTAS ORDER BY nombre' ); define( 'CONSULTAR_PISTA_POR_NOME', 'SELECT nombre, aforo FROM PISTAS WHERE nombre = :nombre' ); define( 'INSERIR_PISTA', 'INSERT INTO PISTAS (nombre, aforo) VALUES (:nombre, :aforo)' ); define( 'ACTUALIZAR_PISTA', 'UPDATE PISTAS SET nombre = :nombre_novo, aforo = :aforo WHERE nombre = :nombre_orixinal' ); define( 'BORRAR_PISTA', 'DELETE FROM PISTAS WHERE nombre = :nombre' );
Arquivo pistas/consultar_pistas.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); $stmt = $conex->query(CONSULTAR_PISTAS); $pistas = $stmt->fetchAll(); } catch (PDOException $e) { error_log($e->getMessage()); die('Erro relacionado coa base de datos.'); } catch (Throwable $e) { error_log($e->getMessage()); die('Produciuse un erro na aplicación.'); } ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Xestión de pistas</title> </head> <body> <h1>Xestión de pistas</h1> <form action="alta_pista.php" method="get"> <button type="submit">Nova pista</button> </form> <table> <thead> <tr> <th>Nome</th> <th>Aforo</th> <th>Accións</th> </tr> </thead> <tbody> <?php foreach ($pistas as $pista): ?> <tr> <td><?= htmlspecialchars($pista['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars((string) $pista['aforo'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td> <a href="editar_pista.php?nombre=<?= urlencode($pista['nombre']) ?>">Editar</a> <form action="procesar_borrar_pista.php" method="post" style="display:inline" onsubmit="return confirm('Estás seguro de que queres borrar esta pista?');" > <input type="hidden" name="nombre" value="<?= htmlspecialchars($pista['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <button type="submit" name="btnBorrarPista" value="BORRAR">Borrar</button> </form> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php if (isset($_GET['msg'])): ?> <p><?= htmlspecialchars($_GET['msg'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></p> <?php endif; ?> <?php if (isset($_GET['error'])): ?> <p><?= htmlspecialchars($_GET['error'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></p> <?php endif; ?> </body> </html>
Arquivo pistas/campos_pista.php
<label for="nombre">Nome:</label> <input type="text" id="nombre" name="nombre" maxlength="50" required value="<?= htmlspecialchars($nombre ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <br> <label for="aforo">Aforo:</label> <input type="number" id="aforo" name="aforo" min="20" max="500" required value="<?= htmlspecialchars(isset($aforo) ? (string) $aforo : '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" >
A validación HTML mellora a experiencia do usuario, pero os mesmos criterios deben comprobarse novamente no servidor.
Arquivo pistas/alta_pista.php
<!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Alta de pista</title> </head> <body> <h1>Alta de pista</h1> <form method="post" action="procesar_alta_pista.php"> <?php require 'campos_pista.php'; ?> <div> <button type="submit" name="btnAltaPista" value="ALTA">Dar de alta</button> </div> </form> </body> </html>
Arquivo pistas/procesar_alta_pista.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnAltaPista']) || $_POST['btnAltaPista'] !== 'ALTA') { throw new RuntimeException('Operación non válida.'); } $nombre = trim($_POST['nombre'] ?? ''); $aforo = filter_input( INPUT_POST, 'aforo', FILTER_VALIDATE_INT, [ 'options' => [ 'min_range' => 20, 'max_range' => 500 ] ] ); if ($nombre === '' || mb_strlen($nombre, 'UTF-8') > 50) { header('Location: consultar_pistas.php?error=' . urlencode('O nome da pista non é válido.')); exit; } if ($aforo === false || $aforo === null) { header('Location: consultar_pistas.php?error=' . urlencode('O aforo non é válido.')); exit; } $stmt = $conex->prepare(INSERIR_PISTA); $stmt->bindValue(':nombre', $nombre, PDO::PARAM_STR); $stmt->bindValue(':aforo', $aforo, PDO::PARAM_INT); $stmt->execute(); header('Location: consultar_pistas.php?msg=' . urlencode('Pista dada de alta correctamente.')); exit; } catch (PDOException $e) { error_log($e->getMessage()); $codigoMySQL = $e->errorInfo[1] ?? null; $mensaxe = $codigoMySQL === 1062 ? 'Xa existe unha pista con ese nome.' : 'Erro relacionado coa base de datos.'; header('Location: consultar_pistas.php?error=' . urlencode($mensaxe)); exit; } catch (Throwable $e) { error_log($e->getMessage()); header('Location: consultar_pistas.php?error=' . urlencode('Produciuse un erro na aplicación.')); exit; }
Arquivo pistas/editar_pista.php
Esta páxina recibe o nome da pista mediante GET. É correcto porque a petición unicamente consulta os datos e amosa o formulario.
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); $nombreRecibido = trim($_GET['nombre'] ?? ''); if ($nombreRecibido === '') { header('Location: consultar_pistas.php?error=' . urlencode('Non se indicou unha pista válida.')); exit; } $stmt = $conex->prepare(CONSULTAR_PISTA_POR_NOME); $stmt->execute([':nombre' => $nombreRecibido]); $pista = $stmt->fetch(); if ($pista === false) { header('Location: consultar_pistas.php?error=' . urlencode('Non se atopou a pista indicada.')); exit; } $nombre = $pista['nombre']; $aforo = $pista['aforo']; } catch (PDOException $e) { error_log($e->getMessage()); die('Erro relacionado coa base de datos.'); } catch (Throwable $e) { error_log($e->getMessage()); die('Produciuse un erro na aplicación.'); } ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Modificar pista</title> </head> <body> <h1>Modificar pista</h1> <form method="post" action="procesar_editar_pista.php"> <?php require 'campos_pista.php'; ?> <input type="hidden" name="nome_orixinal" value="<?= htmlspecialchars($nombre, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <button type="submit" name="btnModificarPista" value="MODIFICAR">Modificar</button> </form> </body> </html>
Fixarse en que gardamos o nome orixinal nun campo oculto porque o usuario pode modificar PISTAS.nombre, que neste modelo é a clave primaria. Un campo hidden pode ser manipulado, polo que o servidor segue tratando o valor como un dato non confiable.
Arquivo pistas/procesar_editar_pista.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnModificarPista']) || $_POST['btnModificarPista'] !== 'MODIFICAR') { throw new RuntimeException('Operación non válida.'); } $nombreNovo = trim($_POST['nombre'] ?? ''); $nombreOrixinal = trim($_POST['nome_orixinal'] ?? ''); $aforo = filter_input( INPUT_POST, 'aforo', FILTER_VALIDATE_INT, [ 'options' => [ 'min_range' => 20, 'max_range' => 500 ] ] ); if ($nombreNovo === '' || mb_strlen($nombreNovo, 'UTF-8') > 50 || $nombreOrixinal === '') { header('Location: consultar_pistas.php?error=' . urlencode('Os datos da pista non son válidos.')); exit; } if ($aforo === false || $aforo === null) { header('Location: consultar_pistas.php?error=' . urlencode('O aforo non é válido.')); exit; } $stmt = $conex->prepare(ACTUALIZAR_PISTA); $stmt->bindValue(':nombre_novo', $nombreNovo, PDO::PARAM_STR); $stmt->bindValue(':aforo', $aforo, PDO::PARAM_INT); $stmt->bindValue(':nombre_orixinal', $nombreOrixinal, PDO::PARAM_STR); $stmt->execute(); $mensaxe = $stmt->rowCount() === 1 ? 'Pista actualizada correctamente.' : 'Non se modificou ningunha fila (pode non existir ou non haber cambios).'; header('Location: consultar_pistas.php?msg=' . urlencode($mensaxe)); exit; } catch (PDOException $e) { error_log($e->getMessage()); $codigoMySQL = $e->errorInfo[1] ?? null; $mensaxe = $codigoMySQL === 1062 ? 'Xa existe unha pista co novo nome indicado.' : 'Erro relacionado coa base de datos.'; header('Location: consultar_pistas.php?error=' . urlencode($mensaxe)); exit; } catch (Throwable $e) { error_log($e->getMessage()); header('Location: consultar_pistas.php?error=' . urlencode('Produciuse un erro na aplicación.')); exit; }
Arquivo pistas/procesar_borrar_pista.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnBorrarPista']) || $_POST['btnBorrarPista'] !== 'BORRAR') { throw new RuntimeException('Operación non válida.'); } $nombre = trim($_POST['nombre'] ?? ''); if ($nombre === '') { header('Location: consultar_pistas.php?error=' . urlencode('Non se indicou unha pista válida.')); exit; } $stmt = $conex->prepare(BORRAR_PISTA); $stmt->execute([':nombre' => $nombre]); $mensaxe = $stmt->rowCount() === 1 ? 'Pista borrada correctamente.' : 'Non se atopou a pista que se quería borrar.'; header('Location: consultar_pistas.php?msg=' . urlencode($mensaxe)); exit; } catch (PDOException $e) { error_log($e->getMessage()); $codigoMySQL = $e->errorInfo[1] ?? null; $mensaxe = $codigoMySQL === 1451 ? 'A pista está relacionada con outros datos e non se pode borrar.' : 'Erro relacionado coa base de datos.'; header('Location: consultar_pistas.php?error=' . urlencode($mensaxe)); exit; } catch (Throwable $e) { error_log($e->getMessage()); header('Location: consultar_pistas.php?error=' . urlencode('Produciuse un erro na aplicación.')); exit; }
Exercicio 2: mantemento da táboa ANIMALES
A táboa ANIMALES incorpora varias características novas:
- relacións con
PISTASeATRACCIONES; - campos que poden conter
NULL; - campos numéricos opcionais;
- listas
<select>cuxas opcións se obteñen da base de datos; - unha clave primaria textual que pode ser modificada polo usuario.
- relacións con
Os campos empregados no exercicio son:
nombre tipo anhos peso estatura nombre_atraccion nombre_pista
Os campos opcionais son:
anhos peso estatura nombre_atraccion nombre_pista
Crearemos o cartafol:
animais/
coas seguintes páxinas:
consultar_animais.php alta_animal.php campos_animal.php procesar_alta_animal.php editar_animal.php procesar_editar_animal.php procesar_borrar_animal.php
- Xestión da táboa ANIMALES
Valores NULL con PDO
Con PDO en PHP representaremos a ausencia de valor mediante null e enlazaremos explicitamente o parámetro como PDO::PARAM_NULL cando sexa necesario:
$stmt->bindValue( ':anhos', null, PDO::PARAM_NULL );
Deste xeito a base de datos recibe un valor SQL NULL, non a cadea de texto 'null'.
Consultas necesarias
Engadimos ao arquivo /var/www/confBD/bd.php:
define( 'CONSULTAR_ATRACCIONS', 'SELECT nombre, fecha_inicio, ganancias FROM ATRACCIONES ORDER BY nombre' ); define( 'CONSULTAR_ANIMAIS', 'SELECT ANIMALES.nombre, ANIMALES.tipo, ANIMALES.anhos, ANIMALES.peso, ANIMALES.estatura, ANIMALES.nombre_atraccion, ATRACCIONES.fecha_inicio, ANIMALES.nombre_pista, PISTAS.aforo FROM ANIMALES LEFT JOIN ATRACCIONES ON ANIMALES.nombre_atraccion = ATRACCIONES.nombre LEFT JOIN PISTAS ON ANIMALES.nombre_pista = PISTAS.nombre ORDER BY ANIMALES.nombre' ); define( 'CONSULTAR_ANIMAL_POR_NOME', 'SELECT nombre, tipo, anhos, peso, estatura, nombre_atraccion, nombre_pista FROM ANIMALES WHERE nombre = :nombre' ); define( 'INSERIR_ANIMAL', 'INSERT INTO ANIMALES ( nombre, tipo, anhos, peso, estatura, nombre_atraccion, nombre_pista ) VALUES ( :nombre, :tipo, :anhos, :peso, :estatura, :nombre_atraccion, :nombre_pista )' ); define( 'ACTUALIZAR_ANIMAL', 'UPDATE ANIMALES SET nombre = :nombre_novo, tipo = :tipo, anhos = :anhos, peso = :peso, estatura = :estatura, nombre_atraccion = :nombre_atraccion, nombre_pista = :nombre_pista WHERE nombre = :nombre_orixinal' ); define( 'BORRAR_ANIMAL', 'DELETE FROM ANIMALES WHERE nombre = :nombre' );
Reutilizaremos tamén CONSULTAR_PISTAS, definida no exercicio anterior.
Arquivo animais/consultar_animais.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); $stmt = $conex->query(CONSULTAR_ANIMAIS); $animais = $stmt->fetchAll(); } catch (PDOException $e) { error_log($e->getMessage()); die('Erro relacionado coa base de datos.'); } catch (Throwable $e) { error_log($e->getMessage()); die('Produciuse un erro na aplicación.'); } ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Xestión de animais</title> </head> <body> <h1>Xestión de animais</h1> <form action="alta_animal.php" method="get"> <button type="submit">Novo animal</button> </form> <table> <thead> <tr> <th>Nome</th> <th>Tipo</th> <th>Anos</th> <th>Peso</th> <th>Estatura</th> <th>Atracción</th> <th>Data inicio</th> <th>Pista</th> <th>Aforo</th> <th>Accións</th> </tr> </thead> <tbody> <?php foreach ($animais as $animal): ?> <?php $fechaInicio = ''; if ($animal['fecha_inicio'] !== null) { $fechaInicio = (new DateTime($animal['fecha_inicio']))->format('d-m-Y'); } ?> <tr> <td><?= htmlspecialchars($animal['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars($animal['tipo'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars((string) ($animal['anhos'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars((string) ($animal['peso'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars((string) ($animal['estatura'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars((string) ($animal['nombre_atraccion'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars($fechaInicio, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars((string) ($animal['nombre_pista'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td><?= htmlspecialchars((string) ($animal['aforo'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></td> <td> <a href="editar_animal.php?nombre=<?= urlencode($animal['nombre']) ?>">Editar</a> <form action="procesar_borrar_animal.php" method="post" style="display:inline" onsubmit="return confirm('Estás seguro de que queres borrar este animal?');" > <input type="hidden" name="nombre" value="<?= htmlspecialchars($animal['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <button type="submit" name="btnBorrarAnimal" value="BORRAR">Borrar</button> </form> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php if (isset($_GET['msg'])): ?> <p><?= htmlspecialchars($_GET['msg'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></p> <?php endif; ?> <?php if (isset($_GET['error'])): ?> <p><?= htmlspecialchars($_GET['error'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></p> <?php endif; ?> </body> </html>
Como empregamos un LEFT JOIN, fecha_inicio pode chegar como NULL. Comprobámolo antes de crear o obxecto DateTime.
Carga das pistas e atraccións dos formularios
Os formularios de alta e modificación necesitan todas as pistas e atraccións dispoñibles.
Arquivo animais/campos_animal.php
<label for="nombre">Nome:</label> <input type="text" id="nombre" name="nombre" required value="<?= htmlspecialchars($nombre ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <br> <label for="tipo">Tipo:</label> <select id="tipo" name="tipo" required> <?php $tipos = [ 'León', 'Cocodrilo', 'Jirafa', 'Mono' ]; ?> <?php foreach ($tipos as $tipoOption): ?> <option value="<?= htmlspecialchars($tipoOption, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" <?= ($tipo ?? '') === $tipoOption ? 'selected' : '' ?> > <?= htmlspecialchars($tipoOption, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?> </option> <?php endforeach; ?> </select> <br> <label for="anhos">Anos:</label> <input type="number" id="anhos" name="anhos" min="1" max="50" value="<?= htmlspecialchars(isset($anhos) ? (string) $anhos : '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <br> <label for="peso">Peso:</label> <input type="number" id="peso" name="peso" min="0.1" max="500" step="any" value="<?= htmlspecialchars(isset($peso) ? (string) $peso : '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <br> <label for="estatura">Estatura:</label> <input type="number" id="estatura" name="estatura" min="0.1" max="20" step="any" value="<?= htmlspecialchars(isset($estatura) ? (string) $estatura : '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <br> <label for="nombre_atraccion">Nome atracción:</label> <select id="nombre_atraccion" name="nombre_atraccion"> <option value="">Sen asignar</option> <?php foreach ($atraccions as $atraccion): ?> <option value="<?= htmlspecialchars($atraccion['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" <?= ($nombre_atraccion ?? null) === $atraccion['nombre'] ? 'selected' : '' ?> > <?= htmlspecialchars($atraccion['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?> </option> <?php endforeach; ?> </select> <br> <label for="nombre_pista">Nome pista:</label> <select id="nombre_pista" name="nombre_pista"> <option value="">Sen asignar</option> <?php foreach ($pistas as $pista): ?> <option value="<?= htmlspecialchars($pista['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" <?= ($nombre_pista ?? null) === $pista['nombre'] ? 'selected' : '' ?> > <?= htmlspecialchars($pista['nombre'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?> </option> <?php endforeach; ?> </select>
A comparación para decidir se unha opción está seleccionada faise cos valores orixinais. O escapado con htmlspecialchars() aplícase unicamente cando o valor se introduce no HTML. Por tanto, os acentos non producen problemas nas comparacións.
Arquivo animais/alta_animal.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); $stmt = $conex->query(CONSULTAR_ATRACCIONS); $atraccions = $stmt->fetchAll(); $stmt = $conex->query(CONSULTAR_PISTAS); $pistas = $stmt->fetchAll(); } catch (PDOException $e) { error_log($e->getMessage()); die('Erro relacionado coa base de datos.'); } catch (Throwable $e) { error_log($e->getMessage()); die('Produciuse un erro na aplicación.'); } ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Alta de animal</title> </head> <body> <h1>Alta de animal</h1> <form method="post" action="procesar_alta_animal.php"> <?php require 'campos_animal.php'; ?> <button type="submit" name="btnAltaAnimal" value="ALTA">Dar de alta</button> </form> </body> </html>
Validación dos datos opcionais
Se un campo opcional chega como cadea baleira, podemos convertelo en null:
$nombrePista = trim( $_POST['nombre_pista'] ?? '' ); $nombrePista = $nombrePista === '' ? null : $nombrePista;
Para un número opcional é mellor distinguir explicitamente a cadea baleira dun valor numérico:
$anhosTexto = trim( $_POST['anhos'] ?? '' ); if ($anhosTexto === '') { $anhos = null; } else { $anhos = filter_var( $anhosTexto, FILTER_VALIDATE_INT, [ 'options' => [ 'min_range' => 1, 'max_range' => 50 ] ] ); }
Non empregaremos empty() como regra xeral para validar números porque tamén considera baleiros valores como 0 e '0'.
Arquivo animais/procesar_alta_animal.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnAltaAnimal']) || $_POST['btnAltaAnimal'] !== 'ALTA') { throw new RuntimeException('Operación non válida.'); } $nombre = trim($_POST['nombre'] ?? ''); $tipo = trim($_POST['tipo'] ?? ''); $tiposPermitidos = ['León', 'Cocodrilo', 'Jirafa', 'Mono']; if ($nombre === '' || !in_array($tipo, $tiposPermitidos, true)) { header('Location: consultar_animais.php?error=' . urlencode('O nome ou o tipo non son válidos.')); exit; } $anhosTexto = trim($_POST['anhos'] ?? ''); $anhos = null; if ($anhosTexto !== '') { $anhos = filter_var( $anhosTexto, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 50]] ); if ($anhos === false) { header('Location: consultar_animais.php?error=' . urlencode('O número de anos non é válido.')); exit; } } $pesoTexto = trim($_POST['peso'] ?? ''); $peso = null; if ($pesoTexto !== '') { $peso = filter_var($pesoTexto, FILTER_VALIDATE_FLOAT); if ($peso === false || $peso < 0.1 || $peso > 500) { header('Location: consultar_animais.php?error=' . urlencode('O peso non é válido.')); exit; } } $estaturaTexto = trim($_POST['estatura'] ?? ''); $estatura = null; if ($estaturaTexto !== '') { $estatura = filter_var($estaturaTexto, FILTER_VALIDATE_FLOAT); if ($estatura === false || $estatura < 0.1 || $estatura > 20) { header('Location: consultar_animais.php?error=' . urlencode('A estatura non é válida.')); exit; } } $nombreAtraccion = trim($_POST['nombre_atraccion'] ?? ''); $nombreAtraccion = $nombreAtraccion === '' ? null : $nombreAtraccion; $nombrePista = trim($_POST['nombre_pista'] ?? ''); $nombrePista = $nombrePista === '' ? null : $nombrePista; $stmt = $conex->prepare(INSERIR_ANIMAL); $stmt->bindValue(':nombre', $nombre, PDO::PARAM_STR); $stmt->bindValue(':tipo', $tipo, PDO::PARAM_STR); $stmt->bindValue( ':anhos', $anhos, $anhos === null ? PDO::PARAM_NULL : PDO::PARAM_INT ); $stmt->bindValue( ':peso', $peso === null ? null : (string) $peso, // $peso es double, pero no existe PARAM_DOUBLE en PDO polo que temos que envialo como String $peso === null ? PDO::PARAM_NULL : PDO::PARAM_STR ); $stmt->bindValue( ':estatura', $estatura === null ? null : (string) $estatura, // $estatura es double, pero no existe PARAM_DOUBLE en PDO polo que temos que envialo como String $estatura === null ? PDO::PARAM_NULL : PDO::PARAM_STR ); $stmt->bindValue( ':nombre_atraccion', $nombreAtraccion, $nombreAtraccion === null ? PDO::PARAM_NULL : PDO::PARAM_STR ); $stmt->bindValue( ':nombre_pista', $nombrePista, $nombrePista === null ? PDO::PARAM_NULL : PDO::PARAM_STR ); $stmt->execute(); header('Location: consultar_animais.php?msg=' . urlencode('Animal dado de alta correctamente.')); exit; } catch (PDOException $e) { error_log($e->getMessage()); $codigoMySQL = $e->errorInfo[1] ?? null; if ($codigoMySQL === 1062) { $mensaxe = 'Xa existe un animal con ese nome.'; } elseif ($codigoMySQL === 1452) { $mensaxe = 'A pista ou a atracción seleccionada non é válida.'; } else { $mensaxe = 'Erro relacionado coa base de datos.'; } header('Location: consultar_animais.php?error=' . urlencode($mensaxe)); exit; } catch (Throwable $e) { error_log($e->getMessage()); header('Location: consultar_animais.php?error=' . urlencode('Produciuse un erro na aplicación.')); exit; }
PDO non dispón dun tipo PDO::PARAM_FLOAT. Neste exemplo os valores decimais non nulos envíanse como cadeas, mentres que os valores ausentes se ligan explicitamente como PDO::PARAM_NULL.
Arquivo animais/editar_animal.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); $nombreRecibido = trim($_GET['nombre'] ?? ''); if ($nombreRecibido === '') { header('Location: consultar_animais.php?error=' . urlencode('Non se indicou un animal válido.')); exit; } $stmt = $conex->prepare(CONSULTAR_ANIMAL_POR_NOME); $stmt->execute([':nombre' => $nombreRecibido]); $animal = $stmt->fetch(); if ($animal === false) { header('Location: consultar_animais.php?error=' . urlencode('Non se atopou o animal indicado.')); exit; } $nombre = $animal['nombre']; $tipo = $animal['tipo']; $anhos = $animal['anhos']; $peso = $animal['peso']; $estatura = $animal['estatura']; $nombre_atraccion = $animal['nombre_atraccion']; $nombre_pista = $animal['nombre_pista']; $stmt = $conex->query(CONSULTAR_ATRACCIONS); $atraccions = $stmt->fetchAll(); $stmt = $conex->query(CONSULTAR_PISTAS); $pistas = $stmt->fetchAll(); } catch (PDOException $e) { error_log($e->getMessage()); die('Erro relacionado coa base de datos.'); } catch (Throwable $e) { error_log($e->getMessage()); die('Produciuse un erro na aplicación.'); } ?> <!DOCTYPE html> <html lang="gl"> <head> <meta charset="UTF-8"> <title>Modificar animal</title> </head> <body> <h1>Modificar animal</h1> <form method="post" action="procesar_editar_animal.php"> <?php require 'campos_animal.php'; ?> <input type="hidden" name="nome_orixinal" value="<?= htmlspecialchars($nombre, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>" > <button type="submit" name="btnModificarAnimal" value="MODIFICAR">Modificar</button> </form> </body> </html>
Os valores recuperados da base de datos mantéñense sen codificar nas variables. O escapado faise unicamente no momento de xerar a saída HTML, polo que as comparacións dos select funcionan correctamente aínda que existan acentos.
Arquivo animais/procesar_editar_animal.php
O tratamento dos campos opcionais é idéntico ao realizado na alta.
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnModificarAnimal']) || $_POST['btnModificarAnimal'] !== 'MODIFICAR') { throw new RuntimeException('Operación non válida.'); } $nombreNovo = trim($_POST['nombre'] ?? ''); $nombreOrixinal = trim($_POST['nome_orixinal'] ?? ''); $tipo = trim($_POST['tipo'] ?? ''); $tiposPermitidos = ['León', 'Cocodrilo', 'Jirafa', 'Mono']; if ($nombreNovo === '' || $nombreOrixinal === '' || !in_array($tipo, $tiposPermitidos, true)) { header('Location: consultar_animais.php?error=' . urlencode('O nome ou o tipo non son válidos.')); exit; } $anhosTexto = trim($_POST['anhos'] ?? ''); $anhos = null; if ($anhosTexto !== '') { $anhos = filter_var( $anhosTexto, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 50]] ); if ($anhos === false) { header('Location: consultar_animais.php?error=' . urlencode('O número de anos non é válido.')); exit; } } $pesoTexto = trim($_POST['peso'] ?? ''); $peso = null; if ($pesoTexto !== '') { $peso = filter_var($pesoTexto, FILTER_VALIDATE_FLOAT); if ($peso === false || $peso < 0.1 || $peso > 500) { header('Location: consultar_animais.php?error=' . urlencode('O peso non é válido.')); exit; } } $estaturaTexto = trim($_POST['estatura'] ?? ''); $estatura = null; if ($estaturaTexto !== '') { $estatura = filter_var($estaturaTexto, FILTER_VALIDATE_FLOAT); if ($estatura === false || $estatura < 0.1 || $estatura > 20) { header('Location: consultar_animais.php?error=' . urlencode('A estatura non é válida.')); exit; } } $nombreAtraccion = trim($_POST['nombre_atraccion'] ?? ''); $nombreAtraccion = $nombreAtraccion === '' ? null : $nombreAtraccion; $nombrePista = trim($_POST['nombre_pista'] ?? ''); $nombrePista = $nombrePista === '' ? null : $nombrePista; $stmt = $conex->prepare(ACTUALIZAR_ANIMAL); $stmt->bindValue(':nombre_novo', $nombreNovo, PDO::PARAM_STR); $stmt->bindValue(':tipo', $tipo, PDO::PARAM_STR); $stmt->bindValue( ':anhos', $anhos, $anhos === null ? PDO::PARAM_NULL : PDO::PARAM_INT ); $stmt->bindValue( ':peso', $peso === null ? null : (string) $peso, $peso === null ? PDO::PARAM_NULL : PDO::PARAM_STR ); $stmt->bindValue( ':estatura', $estatura === null ? null : (string) $estatura, $estatura === null ? PDO::PARAM_NULL : PDO::PARAM_STR ); $stmt->bindValue( ':nombre_atraccion', $nombreAtraccion, $nombreAtraccion === null ? PDO::PARAM_NULL : PDO::PARAM_STR ); $stmt->bindValue( ':nombre_pista', $nombrePista, $nombrePista === null ? PDO::PARAM_NULL : PDO::PARAM_STR ); $stmt->bindValue(':nombre_orixinal', $nombreOrixinal, PDO::PARAM_STR); $stmt->execute(); $mensaxe = $stmt->rowCount() === 1 ? 'Animal actualizado correctamente.' : 'Non se modificou ningunha fila (pode non existir ou non haber cambios).'; header('Location: consultar_animais.php?msg=' . urlencode($mensaxe)); exit; } catch (PDOException $e) { error_log($e->getMessage()); $codigoMySQL = $e->errorInfo[1] ?? null; if ($codigoMySQL === 1062) { $mensaxe = 'Xa existe un animal co novo nome indicado.'; } elseif ($codigoMySQL === 1452) { $mensaxe = 'A pista ou a atracción seleccionada non é válida.'; } else { $mensaxe = 'Erro relacionado coa base de datos.'; } header('Location: consultar_animais.php?error=' . urlencode($mensaxe)); exit; } catch (Throwable $e) { error_log($e->getMessage()); header('Location: consultar_animais.php?error=' . urlencode('Produciuse un erro na aplicación.')); exit; }
Arquivo animais/procesar_borrar_animal.php
<?php try { require '/var/www/confBD/bd.php'; $dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=utf8mb4', SERVIDOR, BD ); $conex = new PDO( $dsn, USUARIO, PASSWORD, [ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false ] ); if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['btnBorrarAnimal']) || $_POST['btnBorrarAnimal'] !== 'BORRAR') { throw new RuntimeException('Operación non válida.'); } $nombre = trim($_POST['nombre'] ?? ''); if ($nombre === '') { header('Location: consultar_animais.php?error=' . urlencode('Non se indicou un animal válido.')); exit; } $stmt = $conex->prepare(BORRAR_ANIMAL); $stmt->execute([':nombre' => $nombre]); $mensaxe = $stmt->rowCount() === 1 ? 'Animal borrado correctamente.' : 'Non se atopou o animal que se quería borrar.'; header('Location: consultar_animais.php?msg=' . urlencode($mensaxe)); exit; } catch (PDOException $e) { error_log($e->getMessage()); $codigoMySQL = $e->errorInfo[1] ?? null; $mensaxe = $codigoMySQL === 1451 ? 'O animal está relacionado con outros datos e non se pode borrar.' : 'Erro relacionado coa base de datos.'; header('Location: consultar_animais.php?error=' . urlencode($mensaxe)); exit; } catch (Throwable $e) { error_log($e->getMessage()); header('Location: consultar_animais.php?error=' . urlencode('Produciuse un erro na aplicación.')); exit; }
mysqli como ampliación
PDO é o mecanismo principal empregado nestes apuntes. A extensión mysqli tamén permite realizar estas operacións correctamente, pero empregaremos igualmente consultas preparadas.
Conexión
<?php try { require '/var/www/confBD/bd.php'; mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT ); $conex = new mysqli( SERVIDOR, USUARIO, PASSWORD, BD ); $conex->set_charset('utf8mb4'); // Operacións... } catch (mysqli_sql_exception $e) { error_log($e->getMessage()); die('Erro relacionado coa base de datos.'); } catch (Throwable $e) { error_log($e->getMessage()); die('Produciuse un erro na aplicación.'); }
INSERT preparado
$stmt = $conex->prepare( 'INSERT INTO PISTAS (nombre, aforo) VALUES (?, ?)' ); $stmt->bind_param( 'si', $nombre, $aforo ); $stmt->execute();
SELECT preparado
$stmt = $conex->prepare( 'SELECT nombre, aforo FROM PISTAS WHERE nombre = ?' ); $stmt->bind_param( 's', $nombre ); $stmt->execute(); $resultado = $stmt->get_result(); $pista = $resultado->fetch_assoc();
UPDATE preparado
$stmt = $conex->prepare( 'UPDATE PISTAS SET nombre = ?, aforo = ? WHERE nombre = ?' ); $stmt->bind_param( 'sis', $nombreNovo, $aforo, $nombreOrixinal ); $stmt->execute();
DELETE preparado
$stmt = $conex->prepare( 'DELETE FROM PISTAS WHERE nombre = ?' ); $stmt->bind_param( 's', $nombre ); $stmt->execute();
Non empregaremos como patrón principal real_escape_string() combinado con sprintf() para introducir valores nunha orde SQL.
Exercicios propostos
Se realizaches os exercicios anteriores xa tes unha estrutura que podes reutilizar para o mantemento doutras táboas.
Podes escoller outra táboa e implementar listado, alta, modificación, borrado, validación no servidor, consultas preparadas e tratamento de erros.
Exercicio con autores e libros
Dado o nome e apelidos dun autor seleccionado nun elemento <select>, amosar nunha táboa a lista de títulos escritos polo autor, ordenados polo título.
Se o autor non ten libros deberá aparecer:
Sen libros escritos
O identificador do autor debe enviarse mediante unha consulta preparada.
Listado de libros con paxinación
Amosar os libros con paxinación. Asociar a cada libro un elemento que permita seleccionalo e, ao realizar a selección, amosar os exemplares dese libro coa imaxe, prezo e estado.
Busca de libros
Dispoñer dunha caixa de texto na que o usuario escriba parte do título dun libro.
Realizar unha consulta con LIKE e amosar os libros que coincidan co patrón xunto co número de exemplares de cada libro.
O patrón constrúese como valor PHP:
$patron = '%' . $texto . '%'; $stmt = $conex->prepare( 'SELECT ... WHERE titulo LIKE :patron' ); $stmt->execute([ ':patron' => $patron ]);
Os caracteres % forman parte do valor do parámetro; a entrada do usuario non se concatena dentro da orde SQL.
Mantemento dunha relación N:M
Nunha relación N:M podemos seleccionar un elemento da primeira táboa, consultar todos os elementos da segunda e amosar un checkbox por cada un deles.
Por exemplo:
- seleccionar unha atracción;
- amosar todos os artistas;
- marcar os artistas que traballan nela;
- permitir engadir ou eliminar asociacións.
Como unha única acción do usuario pode provocar varios INSERT e DELETE, este tipo de operación é un bo exemplo para empregar unha transacción:
$conex->beginTransaction(); try { // DELETE das relacións eliminadas. // INSERT das novas relacións. $conex->commit(); } catch (Throwable $e) { if ($conex->inTransaction()) { $conex->rollBack(); } throw $e; }
Deste modo todas as modificacións se confirman xuntas ou se desfán se se produce un erro.
Resumo
- Empregaremos PDO como mecanismo principal de acceso á base de datos.
- O arquivo
/var/www/confBD/bd.phpqueda fóra de/var/www/html. - As credenciais e as consultas estarán centralizadas nese arquivo.
- As consultas con datos variables realizaranse con
prepare(). - Non introduciremos datos do usuario na SQL mediante
sprintf(), concatenación ou interpolación. - Os datos validaranse sempre no servidor.
- O escapado HTML realizarase no momento de xerar a saída mediante
htmlspecialchars(). - Non gardaremos entidades HTML na base de datos como mecanismo xeral de seguridade.
- As operacións que modifican datos empregarán
POST. - A protección CSRF incorporarase cando se estuden as sesións.
- Os campos
hiddenpoden ser manipulados e deben tratarse como datos non confiables. - As claves primarias modificables requiren conservar o valor orixinal para localizar a fila.
- Os conflitos de clave duplicada deben estar garantidos pola base de datos e tratados pola aplicación.
- Os valores opcionais representaranse en PHP mediante
null. - Con PDO empregaremos
PDO::PARAM_NULLcando necesitemos ligar explicitamente un valor SQLNULL. - Non construiremos manualmente a cadea
'null'nin faremosstr_replace()sobre a consulta. - Os valores usados para decidir unha opción
selectedcompararanse na súa forma orixinal. htmlspecialchars()aplicarase só cando xeremos o atributo ou texto HTML correspondente.- Os erros técnicos rexistraranse con
error_log(). - Non mostraremos directamente
$e->getMessage()ao usuario. mysqliqueda como ampliación, tamén empregando consultas preparadas.
Comprobación final dos exercicios
Antes de dar por rematada a práctica comprobaremos que:
- As listaxes funcionan sen parámetros e as consultas dunha fila empregan
prepare()eexecute(). - O alta valida os campos no servidor, insire mediante
POSTe trata as claves duplicadas. - A modificación conserva a clave primaria orixinal e permite cambiar o nome da pista ou do animal.
- O borrado só se realiza mediante
POSTe trata posibles conflitos de integridade referencial. - Os campos opcionais baleiros gárdanse como
NULLcando corresponde e non como texto'null'. - As etiquetas, textos e atributos HTML amosan os datos mediante
htmlspecialchars(). - Non se mostran credenciais nin mensaxes técnicas de excepción no navegador.
- As probas inclúen entradas mal formadas, valores límite e rexistros inexistentes.
Ampliación dos exercicios
- Realizar unha segunda versión dos exercicios anteriores nas páxinas de 'procesar....' para que se chame a un procedemento almacenado de Mysql.
- Define a chamada ao procedemento o os parámetros necesarios no arquivo bd.php.