Diferencia entre revisiones de «PHP UD3 EXERCICIOS PROPOSTOS»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
(No se muestran 7 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 unha base de datos MySQL/MariaDB.
+
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> como ampliación para poder interpretar e realizar código equivalente con esa extensión.
+
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.'
−
         : 'A operación realizouse, pero non foi necesario modificar ningún dato.';
+
         : '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 ===
  
−
Nos apuntes antigos era necesario construír manualmente a consulta e aparecía un patrón similar a:
+
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:
−
 
 
−
::<syntaxhighlight lang="php" enclose="div">
 
−
if (empty($anhos)) {
 
−
    $anhos = 'null';
 
−
}
 
−
 
 
−
$consulta = sprintf(...);
 
−
 
 
−
$consulta = str_replace(
 
−
    "'null'",
 
−
    "null",
 
−
    $consulta
 
−
);
 
−
</syntaxhighlight>
 
−
 
 
−
Con PDO e consultas preparadas '''non faremos isto'''.
 
−
 
 
−
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:
 
  
 
::<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 1066: Línea 1066:
 
Os formularios de alta e modificación necesitan todas as pistas e atraccións dispoñibles.
 
Os formularios de alta e modificación necesitan todas as pistas e atraccións dispoñibles.
  
−
Nos apuntes antigos o propio arquivo <code>campos_animal.php</code> abría unha conexión coa base de datos. Nesta versión serán <code>alta_animal.php</code> e <code>editar_animal.php</code> as páxinas que cargarán os arrays <code>$pistas</code> e <code>$atraccions</code> antes de incluír o fragmento do formulario.
 
  
−
Desta forma <code>campos_animal.php</code> limítase a xerar os campos do formulario.
 
  
 
<br />
 
<br />
Línea 1201: 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 1305: 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 1382: Línea 1382:
 
     $stmt->bindValue(
 
     $stmt->bindValue(
 
         ':peso',
 
         ':peso',
−
         $peso === null ? null : (string) $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
 
         $peso === null ? PDO::PARAM_NULL : PDO::PARAM_STR
 
     );
 
     );
Línea 1388: Línea 1388:
 
     $stmt->bindValue(
 
     $stmt->bindValue(
 
         ':estatura',
 
         ':estatura',
−
         $estatura === null ? null : (string) $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
 
         $estatura === null ? PDO::PARAM_NULL : PDO::PARAM_STR
 
     );
 
     );
Línea 1458: 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 1553: 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 1659: Línea 1661:
 
     $mensaxe = $stmt->rowCount() === 1
 
     $mensaxe = $stmt->rowCount() === 1
 
         ? 'Animal actualizado correctamente.'
 
         ? 'Animal actualizado correctamente.'
−
         : 'A operación realizouse, pero non foi necesario modificar ningún dato.';
+
         : '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 1711: 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 1989: 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.
  
−
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2026).
 
  
  
  
−
<br />
+
== Comprobación final dos exercicios ==
−
* <u>Notas:</u>
 
−
:* A base de datos que imos empregar para facer os exercicios propostos a emprego para explicar outro módulo (Bases de datos) polo que non segue as [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Regras_de_nomeado_de_t.C3.A1boas_e_columnas regras de nomenclatura que indiqué anteriormente]. Podedes deixala así ou modificar o script para adaptalo a ditas regras.
 
−
:* Vou propoñer varios exercicios. O normal sería ter unha estrutura lóxica de cartafoles, onde gardaríamos as páxinas php que conforman o noso sitio web, e tamén unha cabeceira cun menú onde poderíamos navegar ata as diferentes páxinas (lembrade [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Reutilizaci%C3%B3n_de_c%C3%B3digo:_Include_/_Require o emprego de include]).
 
−
:* De cada exercicio podedes facer varias versións, facendo que as operacións contra a base de datos se fagan con ordes SQL ou con chamadas a procedementos creados previamente no xestor de Mysql.
 
−
:* Os exercicios terán código javascript, para confirmar o borrado das filas.
 
  
−
:* Estes exercicios están pensados para aprender PHP.
+
Antes de dar por rematada a práctica comprobaremos que:
−
:: O deseño das páxinas para amosar cun deseño adecuado os formularios non se ten en conta. Iso quedaría para outro curso de CSS, HTML, Javascript / BootStrap / Outro framework cliente.
 
  
−
 
+
# As listaxes funcionan sen parámetros e as consultas dunha fila empregan <code>prepare()</code> e <code>execute()</code>.
−
<br />
+
# O alta valida os campos no servidor, insire mediante <code>POST</code> e trata as claves duplicadas.
−
* Todos os exercicios propostos están baseados nunha base de datos de nome CIRCO probado sobre un xestor MYSQL versión 5.7 ou superior:
+
# A modificación conserva a clave primaria orixinal e permite cambiar o nome da pista ou do animal.
−
[[Image:Mod_BD_Prog_Modelo_Circo.jpg|700px|center]]
+
# 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>.
−
:* Fichero .sql: [[File:CIRCO.zip]]
+
# As etiquetas, textos e atributos HTML amosan os datos mediante <code>htmlspecialchars()</code>.
−
<br />
+
# 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.
  
  
Línea 2015: Línea 2012:
  
 
<br />
 
<br />
−
* '''PASOS A SEGUIR E CONSIDERACIÓNS:'''
+
== Ampliación dos exercicios ==
−
:* Crear a base de datos anterior (CIRCO) executando o script no xestor MYSQL
 
−
:* Crear un usuario/password con permisos de acceso a dita base de datos (sempre dádelle os mínimos permisos posibles, neste caso, serán os de SELECT/UPDATE/DELETE/INSERT/EXECUTE de toda a base de datos)
 
−
:* Crear nun arquivo separado do sitio web de nome '''bd_pistas.php''' todos os datos necesarios para conectarse á base de datos e onde van estar definidas as constantes coas ordes SQL, tal como [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Gardando_os_datos_de_conexi.C3.B3n_nun_lugar_seguro vimos nun punto anterior desta sección].
 
−
:* Na maior parte das bases de datos, as claves primarias son id´s autonuméricos que non se amosan ao usuario e quedan en campos ocultos. Á hora de actualizar os datos dunha táboa o id se emprega para buscar o dato a actualizar e nunca o pode cambiar o usuario no formulario. Nesta base de datos, as claves primarias son datos que se amosan ao usuario e os vai poder cambiar. Polo tanto nos formularios de modificación imos ter que enviar a clave primaria orixinal (sen cambiar) e a clave primaria modificada xa que imos ter que buscar a fila pola clave primaria sen modificar e cambiar o seu valor.
 
−
:* Relacionado có anterior, podería darse o caso de que ao modificar un dato, a clave primaria estivera repetida. Nas solucións non contemplo este caso. A solución é doada: Ou facer unha consulta previa para verificar que a nova clave primaria non existe ou capturar a excepción 'duplicate key' e amosar unha mensaxe ao usuario.
 
−
:* Lembrar que en todos os exercicios aparecen as ordes para amosar os erros que se poidan dar. Nunha aplicación final esas ordes hai que quitalas. E tamén se visualizan os erros que se producen cando hai unha excepción (sección catch). Isto tería que modificarse xa que ao usuario non se lle deben amosar información sobre os erros que se poidan dar.
 
−
:* Lembrar dar permiso 644 (rw,r,r) aos arquivos php e cambiar o propietario a www-data:www-data. Nos cartafoles permisos 755 cando rematedes.
 
−
:* Ao principio ides faiar en moitas cousas. Lembrade ter posto o display_errors como vimos nas seccións anteriores. Tamén axuda moito facer un echo da sentenza a enviar á base de datos antes de enviala para ver que estea todo ben (espazos en branco, comillas,...).
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
 
 
−
==Para todos os exercicios==
 
−
 
 
−
'''Arquivo bd_pistas.php''': Dito arquivo se atopa nun cartafol /var/www/confBD fora da estrutura do sitio web (por defecto é /var/www/html.
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
 
 
−
define ('SERVIDOR','localhost');
 
−
define ('USUARIO','user_php');
 
−
define ('PASSWORD','user_php');
 
−
define ('BD','CIRCO');
 
−
 
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
 
 
−
==Exercicio 1==
 
−
 
 
−
* Partindo da táboa PISTAS, crea unha páxina web que permita dar de alta, baixa, modificar e listar os datos de dita táboa.
 
−
: No listado amosa unha táboa cós datos e na parte dereita un enlace para modificar unha pista e un botón para borrala. Se sabes, fai que o botón teña a apariencia dun enlace empregando css. Se sabes javascript, fai que o botón pida conformación de borrado.
 
−
: <u>Crea un cartafol  pistas</u> onde van estar todas as páxinas relacioanadas con pistas.
 
−
: Como as operacións de alta e modificar van amosar os mesmos campos, fai que os campos estean gardados nun arquivo separado de nome 'campos_pistas.php'. Deberán amosar o dato en caso de que carguemos o formulario ao editar unha pista ou amosar os campos en branco no caso de que viñamos de dar de alta unha nova pista. Isto o podes facer empregando variables que veñan cos datos en caso de editar e se ditas variables non están definidas é que vimos do formulario de alta.
 
−
: Fai que o action do formulario de alta pista, apunte a unha páxina de nome procesar_alta_pista.php que é onde terás o código para dar de alta unha pista. Ao dala de alta terás que redirexionar á páxinas de listado de pistas.
 
−
: Fai que o action do formulario de modificar pista, apunte a unha páxina de nome procesar_modificar_pista.php que é onde terás o código para modificar unha pista. Ao modificala terás que redirexionar á páxinas de listado de pistas.
 
−
: Polo tanto as páxinas a facer son: consultar_pistas.php, alta_pista.php, procesar_alta_pista.php, editar_pista.php, procesar_editar_pista.php, procesar_borrar_pista.php
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<gallery caption="Xestión da táboa PISTAS" widths="350" heights="300px" perrow="2">
 
−
Image:UD3_op_bd_ex_1_A.jpg| Listado de pistas.
 
−
Image:UD3_op_bd_ex_1_B.jpg| Formulario de alta de pista.
 
−
Image:UD3_op_bd_ex_1_C.jpg| Formulario de modificación de pista.
 
−
Image:UD3_op_bd_ex_1_D.jpg| Confirmación borrado de pista.
 
−
</gallery>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
* Como facer o exercicio ?
 
−
: O procedemento sempre é o mesmo:
 
−
:* Definimos nun arquivo externo ao sitio web (se non está feito de antes) os datos necesarios para conectarnos <u>cós permisos mínimos necesarios</u> á base de datos.
 
−
:: Nese arquivo definimos en forma de constantes e con parámetros %? os ordes SQL que despois serán sustituidos polos valores empregando sprintf.
 
−
 
 
−
:* Empezaremos coa páxina de consultar_nometaboa no que debe aparecer en forma de lista (normalmente táboa) os datos da táboa xestionar.
 
−
:: Dita páxina necesará unha constante definida no arquivo anterior para obter a lista de datos da táboa <u>ordenados</u>.
 
−
:: Visualmente permitirá crear un novo dato, modificar un dato existente ou dar de baixa dito dato.
 
−
 
 
−
:* Creamos a páxina de campos_nometaboa. Estarán os elementos do formulario (non o formulario) que se empregará na páxina de alta e de modificar. Como se carga en ambos casos, cando vimos de modificar, teremos acceso á variables que teñen os datos da fila seleccionada. Se vimos de alta, non teremos definidos dichas variables. Polo tanto no value dos elementos do formulario teremos que preguntar se temos ou non definido a variable para darlle un valor por defecto ou o valor da base de datos.
 
−
 
 
−
:* Creamos a páxina alta_nometaboa. Terá o formulario e un botón de alta e fará o include da páxina de campos. Chamará a unha páxina procesar_alta_nomtaboa.
 
−
:* Creamos a páxina procesar_alta_taboa: Collerá os datos do formulario e fará uso dunha constante definada no arquivo de configuración para facer o INSERT.
 
−
:* Creamos a páxina editar_nometaboa. Terá o formulario e un botón de modificar e fará o include da páxina de campos. Virá coa clave primaria do dato a buscar (dende a páxina de consultar), buscaremos os datos da fila e os gardaremos en variables que serán empregadas polo páxina de campos. Se o usuario pode modificar a clave primaria no formulario, gardaremos nun campo oculto o dato orixinal. Chamará a unha páxina procesar_editar_nomtaboa.
 
−
:* Creamos a páxina procesar_editar_taboa: Collerá os datos do formulario e fará uso dunha constante definada no arquivo de configuración para facer o UPDATE.
 
−
:* Creamos a páxina procesar_borrar_taboa: Collerá o dato do elemento a borrar que será enviado por $_GET ou $_POST dende a páxina de consultar e fará uso dunha constante definada no arquivo de configuración para facer o DELETE.
 
−
 
 
−
 
 
−
<br />
 
−
===Solución Exercicio 1===
 
−
'''Arquivo bd_pistas.php''': Engadir o seguinte contido ao existente.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
<?php
 
−
..............
 
−
 
 
−
define('CONSULTAR_PISTAS', 'SELECT nombre,aforo FROM PISTAS ORDER BY nombre');
 
−
define('ACTUALIZAR_PISTAS', "UPDATE PISTAS SET nombre='%s',aforo = %d WHERE nombre = '%s'");
 
−
define('INSERTAR_PISTAS', "INSERT INTO PISTAS (nombre, aforo) VALUES ('%s', %d)");
 
−
define('BORRAR_PISTAS', "DELETE FROM PISTAS WHERE nombre='%s'");
 
−
define('CONSULTAR_PISTAS_POR_NOME', "SELECT nombre,aforo FROM PISTAS WHERE nombre = '%s'");
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br/>
 
−
 
 
−
'''Arquivo '''pistas/consultar_pistas.php''': Fixarse que está dentro dun cartafol creado previamente de nome pistas.
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
 
 
−
try {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); 
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
?>
 
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
 
−
    <head>
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Xestión de pistas</title>
 
−
    </head>
 
−
    <body>
 
−
        <form action="alta_pista.php" method="post">
 
−
            <button type="submit">Nova Pista</button>
 
−
        </form>
 
−
        <table border="1">
 
−
            <tr>
 
−
            <th>Nombre</th>
 
−
            <th>Aforo</th>
 
−
            <th>Accións</th>
 
−
            </tr>
 
−
        <?php
 
−
            $resultado = $conex->query(CONSULTAR_PISTAS);
 
−
 
 
−
            while($fila = $resultado->fetch_assoc()) {
 
−
                $nombre = htmlentities($fila['nombre']);
 
−
                $aforo = htmlentities($fila['aforo']);
 
−
                echo "<tr>";
 
−
                echo "<td>$nombre</td>";
 
−
                echo "<td>$aforo</td>";
 
−
                printf("<td><a href='editar_pista.php?nombre=%s'>Editar</a>",urlencode($fila['nombre']));
 
−
                printf("<form action='procesar_borrar_pista.php' method='post'>");
 
−
                printf("<input type='hidden' name='nombre' value='%s' />",htmlentities($fila['nombre']));
 
−
                echo '<input onclick="return confirm(\'Estás seguro de que queres borrar esta pista?\');" type="submit" value="Borrar" name="btnBorrarPista" style="background:none;border:none;padding:0;maring:0;color: blue; text-decoration:underline; cursor:pointer; font-size:inherit" />';
 
−
                echo '</form>';
 
−
                echo "</tr>";
 
−
            }
 
−
 
 
−
            $resultado->free();
 
−
        ?>   
 
−
        </table>
 
−
 
 
−
 
 
−
       
 
−
    </body>   
 
−
</html>
 
−
<?php
 
−
}  // Pecha o try da parte superior da páxina
 
−
catch (mysqli_sql_exception $e) {
 
−
    die("Erro mysql: " . $e->getMessage());
 
−
}
 
−
catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo pistas/campos_pista.php''':
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<label for="nombre">Nome:</label>
 
−
<input type="text" maxlength="50" id="nombre" name="nombre" required value="<?php echo isset($nombre) ? htmlentities($nombre) : ''; ?>"><br>
 
−
<label for="aforo">Aforo:</label>
 
−
<input type="number" min="20" max="500" id="aforo" name="aforo" required value="<?php echo isset($aforo) ? htmlentities($aforo) : ''; ?>">
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo pistas/alta_pista.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<!DOCTYPE html>
 
−
<html>
 
−
<head>
 
−
  <title>Alta Pista</title>
 
−
</head>
 
−
<body>
 
−
  <h3>Alta pista</h3>
 
−
  <form method="POST" action="procesar_alta_pista.php">
 
−
    <?php include 'campos_pista.php'; ?>
 
−
    <div>
 
−
        <input type="submit" value="Dar de Alta" name="btnAltaPista">
 
−
    </div>
 
−
  </form>
 
−
</body>
 
−
</html>
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo pistas/procesar_alta_pista.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
if (isset($_POST['btnAltaPista'])) {
 
−
  try {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
    // Lóxica para dar de alta unha nova pista
 
−
    $nombre = $conex->real_escape_string($_POST['nombre']);
 
−
    $aforo = $conex->real_escape_string($_POST['aforo']);
 
−
    $consulta = sprintf(INSERTAR_PISTAS,$nombre, $aforo);
 
−
    $conex->query($consulta);
 
−
 
 
−
  } catch (mysqli_sql_exception $e) {
 
−
    die("Erro: " . $e->getMessage());
 
−
  }
 
−
  catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
  }
 
−
  finally{
 
−
    if(!empty($conex)){
 
−
      $conex->close();
 
−
    } 
 
−
  }
 
−
 
 
−
}
 
−
header("Location: consultar_pistas.php");
 
−
 
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo pistas/editar_pista.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
$conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
$conex->set_charset('utf8mb4');
 
−
$erros = '';
 
−
 
 
−
?>
 
−
 
 
−
<?php
 
−
    $consulta = sprintf(CONSULTAR_PISTAS_POR_NOME,$conex->real_escape_string($_GET['nombre']));
 
−
    $result=$conex->query($consulta);  // Ven na URL da paxina consultar_pistas.php
 
−
    if ($result && $result->num_rows>0){
 
−
        $row = mysqli_fetch_assoc($result); // Os datos serán empregados polos inputs do formulario
 
−
        $nombre = htmlentities($row['nombre']);
 
−
        $aforo = htmlentities($row['aforo']);
 
−
 
 
−
        $result->free();
 
−
    }
 
−
    else {
 
−
        $erros = "Non se atopan os datos da pista!!!!";
 
−
    }
 
−
 
 
−
 
 
−
?>
 
−
<!DOCTYPE html>
 
−
<html>
 
−
<head>
 
−
  <title>Modificar Pista</title>
 
−
</head>
 
−
<body> 
 
−
  <h3>Modificar pista</h3>
 
−
 
 
−
  <form method="POST" action="procesar_editar_pista.php">
 
−
    <?php include 'campos_pista.php'; ?>
 
−
    <div>
 
−
        <input type="hidden" name="nome_orixinal" value="<?php echo $nombre; ?>">
 
−
        <input type="submit" value="Modificar" name="btnModificarPista">
 
−
    </div>
 
−
  </form>
 
−
  <?php
 
−
    if(!empty($erros)){
 
−
        printf("<h3>$erros</h3>");
 
−
    }
 
−
  ?>
 
−
</body>
 
−
</html>
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo pistas/procesar_editar_pista.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
$conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
$conex->set_charset('utf8mb4');
 
−
 
 
−
if (isset($_POST['btnModificarPista'])) {
 
−
  try {
 
−
 
 
−
    // Lóxica para dar de alta unha nova pista
 
−
    $nome_novo = $conex->real_escape_string($_POST['nombre']);
 
−
    $nome_orixinal = $conex->real_escape_string($_POST['nome_orixinal']); // Ven nun campo oculto do form
 
−
    $aforo = $conex->real_escape_string($_POST['aforo']);
 
−
    $consulta = sprintf(ACTUALIZAR_PISTAS,$nome_novo, $aforo,$nome_orixinal);
 
−
    $conex->query($consulta);
 
−
 
 
−
  } catch (mysqli_sql_exception $e) {
 
−
    die("Erro: " . $e->getMessage());
 
−
  }
 
−
  catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
  }
 
−
  finally{
 
−
    if(!empty($conex)){
 
−
      $conex->close();
 
−
    } 
 
−
  }
 
−
}
 
−
header("Location: consultar_pistas.php");
 
−
 
 
−
?>
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo pistas/procesar_borrar_pista.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
if (isset($_POST['btnBorrarPista'])) {
 
−
  try {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
    // Lóxica para dar de alta unha nova pista
 
−
    $nombre = $conex->real_escape_string($_POST['nombre']);
 
−
    $consulta = sprintf(BORRAR_PISTAS,$nombre);
 
−
    $conex->query($consulta);
 
−
 
 
−
  } catch (mysqli_sql_exception $e) {
 
−
    die("Erro: " . $e->getMessage());
 
−
  }
 
−
  catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
  }
 
−
  finally{
 
−
    if(!empty($conex)){
 
−
      $conex->close();
 
−
    } 
 
−
  }
 
−
 
 
−
}
 
−
header("Location: consultar_pistas.php");
 
−
 
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
 
 
−
==Exercicio 2==
 
−
 
 
−
 
 
−
* '''<u>NOTA IMPORTANTE:</u>''' Este exercicio vai consistir no mantemento da táboa ANIMALES. Dita táboa ten unha serie de particularidades que non tiña a táboa PISTAS.
 
−
:* Ten relacións 1:N con PISTAS e ATRACCIONES.
 
−
:* Ten campos que poden ter valores NULOS (anhos, peso, estatura, nombre_atraccion, nombre_pista)
 
−
:* Nesta BD é empregada no módulo de BD para aprender SQL. Por iso os campos claves son datos para o usuario. Nunha base de datos 'normalmente' as clave primarias son numéricas auto-incrementales.
 
−
 
 
−
 
 
−
* O anterior vai levar consigo as seguintes modificacións con respecto ao exercicio anterior (posibles solucións):
 
−
:* Na operación de INSERT, na cadea que está definida en forma de constante: define('INSERTAR_ANIMAL', "INSERT INTO ANIMALES (nombre, tipo, anhos, peso, estatura, nombre_atraccion, nombre_pista) VALUES ('%s','%s',%s,%s,%s,'%s','%s')");
 
−
:: Como vedes todos os parámetros son %s (tamén os numéricos). Iso é así porque se queremos enviar un valor null e poñemos %d, o vai cambiar polo valor cero.
 
−
:: Cando enviamos un valor null ao parámetro nombre_pista (por exemplo) vai formar a cadea: "INSERT INTO ANIMALES (....., nombre_pista) VALUES (........., 'null') e o que vai enviar á base de datos vai ser a cadea 'null' non o valor null. Vai ser por iso que antes de executar a sentenza, imos sustituir o valor "'null'" por "null".
 
−
 
 
−
 
 
−
* No formulario de alta e modificar, imos empregar un SELECT para cargar todas as pistas e todas as atraccións dispoñibles e que o usuario sexa o que escolla o valor a gardar. No caso da modificación, o valor do campo nombre_pista / nombre_atraccion do animal seleccionado, terá que empregarse para que o select marque como seleccionado dicho valor do conxunto dispoñible.
 
−
: É dicir, teremos un bucle no que iremos xerando as etiquetas <option> de html por cada pista/atraccion, e cando o nome sexa igual ao da pista/atracción do animal, teremos que xerar a etiqueta 'seltected'.
 
−
: Como imos ter que comparar o valor de cada pista/atracción có valor que ven da base de datos para esa animal, non podemos empregar htmlentities xa que cambiaría os acentos por &letracute;
 
−
 
 
−
: Tamén algún dos campos que veñen da base de datos poden ser NULOS, polo que no podemos empregar htmlentities sobre dito valor. Teremos que comprobalo antes.
 
−
 
 
−
 
 
−
* Agora nos formularios de alta/modificar imos ter que acceder á base de datos para buscar todas as pistas e todas as atraccións que imos cargar nos select de dito formulario.
 
−
 
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
* Partindo da táboa ANIMALES, crea unha páxina web que permita dar de alta, baixa, modificar e listar os datos de dita táboa.
 
−
: No listado amosa unha táboa cós datos e na parte dereita un enlace para modificar un animal e un botón para borralo. Se sabes, fai que o botón teña a apariencia dun enlace empregando css. Se sabes javascript, fai que o botón pida conformación de borrado.
 
−
: <u>Crea un cartafol  animais</u> onde van estar todas as páxinas relacioanadas con animais.
 
−
: Como as operacións de alta e modificar van amosar os mesmos campos, fai que os campos estean gardados nun arquivo separado de nome 'campos_animais.php'. Deberán amosar o dato en caso de que carguemos o formulario ao editar un animal ou amosar os campos en branco no caso de que viñamos de dar de alta un novo animal. Isto o podes facer empregando variables que veñan cos datos en caso de editar e se ditas variables non están definidas é que vimos do formulario de alta.
 
−
: Fai que o action do formulario de alta animal, apunte a unha páxina de nome procesar_alta_animal.php que é onde terás o código para dar de alta un novo animal. Ao dala de alta terás que redirexionar á páxinas de listado de animais.
 
−
: Fai que o action do formulario de modificar animal, apunte a unha páxina de nome procesar_modificar_animal.php que é onde terás o código para modificar un animal. Ao modificalo terás que redirexionar á páxinas de listado de animais.
 
−
: Polo tanto as páxinas a facer son: consultar_animais.php, alta_animal.php, procesar_alta_animal.php, editar_animal.php, procesar_editar_animal.php, procesar_borrar_animal.php
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<gallery caption="Xestión da táboa ANIMALES" widths="350" heights="300px" perrow="2">
 
−
Image:UD3_op_bd_ex_2_A.jpg| Listado de animais.
 
−
Image:UD3_op_bd_ex_2_B.jpg| Formulario de alta de animal.
 
−
Image:UD3_op_bd_ex_2_C.jpg| Formulario de modificación de animal.
 
−
Image:UD3_op_bd_ex_2_D.jpg| Confirmación borrado de animal.
 
−
</gallery>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
* Como facer o exercicio ?
 
−
: O procedemento sempre é o mesmo:
 
−
:* Definimos nun arquivo externo ao sitio web (se non está feito de antes) os datos necesarios para conectarnos <u>cós permisos mínimos necesarios</u> á base de datos.
 
−
:: Nese arquivo definimos en forma de constantes e con parámetros %? os ordes SQL que despois serán sustituidos polos valores empregando sprintf.
 
−
 
 
−
:* Empezaremos coa páxina de consultar_nometaboa no que debe aparecer en forma de lista (normalmente táboa) os datos da táboa xestionar.
 
−
:: Dita páxina necesará unha constante definida no arquivo anterior para obter a lista de datos da táboa <u>ordenados</u>.
 
−
:: Visualmente permitirá crear un novo dato, modificar un dato existente ou dar de baixa dito dato.
 
−
 
 
−
:* Creamos a páxina de campos_nometaboa. Estarán os elementos do formulario (non o formulario) que se empregará na páxina de alta e de modificar. Como se carga en ambos casos, cando vimos de modificar, teremos acceso á variables que teñen os datos da fila seleccionada. Se vimos de alta, non teremos definidos dichas variables. Polo tanto no value dos elementos do formulario teremos que preguntar se temos ou non definido a variable para darlle un valor por defecto ou o valor da base de datos.
 
−
 
 
−
:* Creamos a páxina alta_nometaboa. Terá o formulario e un botón de alta e fará o include da páxina de campos. Chamará a unha páxina procesar_alta_nomtaboa.
 
−
:* Creamos a páxina procesar_alta_taboa: Collerá os datos do formulario e fará uso dunha constante definada no arquivo de configuración para facer o INSERT.
 
−
:* Creamos a páxina editar_nometaboa. Terá o formulario e un botón de modificar e fará o include da páxina de campos. Virá coa clave primaria do dato a buscar (dende a páxina de consultar), buscaremos os datos da fila e os gardaremos en variables que serán empregadas polo páxina de campos. Se o usuario pode modificar a clave primaria no formulario, gardaremos nun campo oculto o dato orixinal. Chamará a unha páxina procesar_editar_nomtaboa.
 
−
:* Creamos a páxina procesar_editar_taboa: Collerá os datos do formulario e fará uso dunha constante definada no arquivo de configuración para facer o UPDATE.
 
−
:* Creamos a páxina procesar_borrar_taboa: Collerá o dato do elemento a borrar que será enviado por $_GET ou $_POST dende a páxina de consultar e fará uso dunha constante definada no arquivo de configuración para facer o DELETE.
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
===Solución Exercicio 2===
 
−
'''Arquivo bd_pistas.php''': Engadir o seguinte contido ao existente.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
<?php
 
−
..............
 
−
 
 
−
define('CONSULTAR_ATRACCIONES','SELECT nombre, fecha_inicio, ganancias FROM ATRACCIONES');
 
−
 
 
−
 
 
−
define('CONSULTAR_ANIMALES','SELECT ANIMALES.nombre,tipo, anhos, peso, estatura, nombre_atraccion, fecha_inicio, nombre_pista, aforo ' .
 
−
                            'FROM ANIMALES LEFT OUTER JOIN ATRACCIONES ON (ANIMALES.nombre_atraccion = ATRACCIONES.nombre) ' .
 
−
                            'LEFT OUTER JOIN PISTAS ON (PISTAS.nombre = ANIMALES.nombre_pista) ' .
 
−
                            'ORDER BY nombre');
 
−
define('INSERTAR_ANIMAL', 'INSERT INTO ANIMALES (nombre, tipo, anhos, peso, estatura, nombre_atraccion, nombre_pista) ' .
 
−
                          "VALUES ('%s','%s',%s,%s,%s,'%s','%s')");
 
−
define('CONSULTAR_ANIMAL_POR_NOME','SELECT ANIMALES.nombre,tipo, anhos, peso, estatura, nombre_atraccion, nombre_pista ' .
 
−
                                    "FROM ANIMALES WHERE nombre = '%s'");
 
−
define('ACTUALIZAR_ANIMAL', "UPDATE ANIMALES SET " .
 
−
                            "nombre='%s', tipo='%s', anhos=%s, peso=%s, estatura=%s, nombre_atraccion='%s', nombre_pista='%s' " .
 
−
                            "WHERE nombre='%s'");
 
−
define('BORRAR_ANIMAL', "DELETE FROM ANIMALES WHERE nombre='%s'");
 
−
 
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br/>
 
−
 
 
−
'''Arquivo: animais/consultar_animais.php''': Fixarse que está dentro dun cartafol creado previamente de nome animais.
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
 
 
−
try {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); 
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
?>
 
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
 
−
    <head>
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Xestión de animais</title>
 
−
    </head>
 
−
    <body>
 
−
        <form action="alta_animal.php" method="post">
 
−
            <button type="submit">Novo Animal</button>
 
−
        </form>
 
−
        <table border="1">
 
−
            <tr>
 
−
                <th>Nome</th>
 
−
                <th>Tipo</th>
 
−
                <th>Anos</th>
 
−
                <th>Peso</th>
 
−
                <th>Estatura</th>
 
−
                <th>Nome Atracción</th>
 
−
                <th>Fecha Inicio</th>
 
−
                <th>Nome Pista</th>
 
−
                <th>Aforo</th>
 
−
            </tr>
 
−
        <?php
 
−
            /**
 
−
            * htmlEntities non se pode empregar sobre un valor null e como temos campos que poden vir con null fago esta función
 
−
            * Como imos empregala en varios sitio, podería estar definida un arquivo de utilidades.inc-php
 
−
            */
 
−
            function comprobarNull($valor){
 
−
                $dato =  $valor==null ? '' : $valor;
 
−
                return htmlentities($dato);
 
−
            }
 
−
 
 
−
            $resultado = $conex->query(CONSULTAR_ANIMALES);
 
−
 
 
−
            while($fila = $resultado->fetch_assoc()) {
 
−
                $nombre = comprobarNull($fila['nombre']);
 
−
                $tipo = comprobarNull($fila['tipo']);
 
−
                $anhos = comprobarNull($fila['anhos']);
 
−
                $peso = comprobarNull($fila['peso']);
 
−
                $estatura = comprobarNull($fila['estatura']);
 
−
                $nombre_atraccion = comprobarNull($fila['nombre_atraccion']);
 
−
                $fecha_inicio = (new DateTime(comprobarNull($fila['fecha_inicio'])))->format('d-m-Y');
 
−
                $nombre_pista = comprobarNull($fila['nombre_pista']);
 
−
                $aforo = comprobarNull($fila['aforo']);
 
−
                echo "<tr>";
 
−
                echo "<td>$nombre</td>";
 
−
                echo "<td>$tipo</td>";
 
−
                echo "<td>$anhos</td>";
 
−
                echo "<td>$peso</td>";
 
−
                echo "<td>$estatura</td>";
 
−
                echo "<td>$nombre_atraccion</td>";
 
−
                echo "<td>$fecha_inicio</td>";
 
−
                echo "<td>$nombre_pista</td>";
 
−
                echo "<td>$aforo</td>";
 
−
                printf("<td><a href='editar_animal.php?nombre=%s'>Editar</a>",urlencode($nombre));
 
−
                printf("<form action='procesar_borrar_animal.php' method='post'>");
 
−
                printf("<input type='hidden' name='nombre' value='%s' />",htmlentities($fila['nombre']));
 
−
                echo '<input onclick="return confirm(\'Estás seguro de que queres borrar este animal?\');" type="submit" value="Borrar" name="btnBorrarAnimal" style="background:none;border:none;padding:0;maring:0;color: blue; text-decoration:underline; cursor:pointer; font-size:inherit" />';
 
−
                echo '</form>';
 
−
                echo "</tr>";
 
−
            }
 
−
 
 
−
            $resultado->free();
 
−
        ?>   
 
−
        </table>
 
−
 
 
−
 
 
−
       
 
−
    </body>   
 
−
</html>
 
−
<?php
 
−
}  // Pecha o try da parte superior da páxina
 
−
catch (mysqli_sql_exception $e) {
 
−
    die("Erro mysql: " . $e->getMessage());
 
−
}
 
−
catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo animais/campos_animais.php''':
 
−
<br />
 
−
<u>'''Notas:'''</u>
 
−
:* Este arquivo vai necesitar ir á base de datos para obter a lista de atraccións e a lista de pistas para cargar nos select.
 
−
:* Fixarse que a lista de pistas está obtida empregando fetch_object() en vez de fetch_assoc(), simplemente como exemplo de como podemos empregar unha forma ou outra.
 
−
:: Á hora de recoller os datos, se estamos empregando a opción de obxectos sería da forma: obxecto->propiedade
 
−
 
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
    // Cargamos os datos que necesita o formulario que son a lista de atracciones e a lista de pistas
 
−
 
 
−
// Como a estes datos podes chegar ao dar de alta ou ao modificar un animal, se ves de modificar un animal, terías que vir o nombre_pista e nombre_atraccion  e tipo
 
−
//  de dito animal e facer que o select seleccionara dita opción.
 
−
//  Pero se ves de alta, o select non ten que ter nada seleccionado.
 
−
$nombre_atraccion = isset($nombre_atraccion) ? $nombre_atraccion : '';  // no pongo htmlentities ya que no lo vou a mostrar. Sólo lo empleo para saber cual se debe selecconar en la select. Si lo pongo no va a encontrar el valor a seleccionar si el nombre tiene tildes por ejemplo
 
−
$nombre_pista = isset($nombre_pista) ? $nombre_pista : ''; // no pongo htmlentities ya que no lo vou a mostrar. Sólo lo empleo para saber cual se debe selecconar en la select. Si lo pongo no va a encontrar el valor a seleccionar si el nombre tiene tildes por ejemplo
 
−
$tipo = isset($tipo) ? htmlentities($tipo) : '';
 
−
 
 
−
try {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
    // BUSCAMOS A LISTA DE ATRACCIONS
 
−
    $resultado = $conex->query(CONSULTAR_ATRACCIONES);
 
−
    $atracciones = [];
 
−
    while($fila = $resultado->fetch_assoc()) {  // Poderíamos empregar fecth_object
 
−
        $atracciones[] = $fila;
 
−
    }
 
−
    $resultado->free();
 
−
 
 
−
    // BUSCAMOS A LISTA DE PISTAS
 
−
    $resultado = $conex->query(CONSULTAR_PISTAS);
 
−
    $pistas = [];
 
−
    while($fila = $resultado->fetch_object()) {    // Por poñer un exemplo empregado esta forma
 
−
        $pistas[] = $fila;
 
−
    }
 
−
    $resultado->free();
 
−
 
 
−
  } catch (mysqli_sql_exception $e) {
 
−
    die("Erro: " . $e->getMessage());
 
−
  }
 
−
  catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
  }
 
−
  finally{
 
−
    if(!empty($conex)){
 
−
      $conex->close();
 
−
    } 
 
−
  }
 
−
 
 
−
?>
 
−
   
 
−
    <label for="nombre">Nome:</label>
 
−
    <input type="text" id="nombre" name="nombre" value="<?php echo isset($nombre) ? htmlentities($nombre) : ''; ?>" required>
 
−
    <br>
 
−
 
 
−
    <label for="tipo">Tipo:</label>
 
−
    <select id="tipo" name="tipo" required>
 
−
        <option value="León" <?php if ($tipo === "León") echo "selected"; ?>>León</option>
 
−
        <option value="Cocodrilo" <?php if ($tipo === "Cocodrilo") echo "selected"; ?>>Cocodrilo</option>
 
−
        <option value="Jirafa" <?php if ($tipo === "Jirafa") echo "selected"; ?>>Jirafa</option>
 
−
        <option value="Mono" <?php if ($tipo === "Mono") echo "selected"; ?>>Mono</option>
 
−
    </select>
 
−
    <br>
 
−
 
 
−
    <label for="anhos">Anos:</label>
 
−
    <input type="number" id="anhos" name="anhos" min="1" max="50" value="<?php echo isset($anhos) ? htmlentities($anhos) : ''; ?>">
 
−
    <br>
 
−
 
 
−
    <label for="peso">Peso:</label>
 
−
    <input type="number" id="peso" name="peso" min="0.1" step='any' max="500" value="<?php echo isset($peso) ? htmlentities($peso) : ''; ?>">
 
−
    <br>
 
−
 
 
−
    <label for="estatura">Estatura:</label>
 
−
    <input type="number" id="estatura" name="estatura" min="0.1"  step='any' max="20" value="<?php echo isset($estatura) ? htmlentities($estatura) : ''; ?>">
 
−
    <br>
 
−
 
 
−
    <label for="nombre_atraccion">Nome Atracción:</label>
 
−
    <select id="nombre_atraccion" name="nombre_atraccion">
 
−
        <option value=''>Sen asignar</option>
 
−
        <?php
 
−
            foreach($atracciones as $atraccion_bd){
 
−
                $selected = '';
 
−
                if($atraccion_bd['nombre']==$nombre_atraccion){  // nombre_atraccion está definido arriba
 
−
                    $selected = 'selected';
 
−
                }
 
−
                printf("<option value='%s' %s>%s</option>",$atraccion_bd['nombre'],$selected,$atraccion_bd['nombre']);  // Fixarse que no value ten que ir a clave primaria de táboa
 
−
            }
 
−
        ?>
 
−
    </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_bd){
 
−
                $selected = '';
 
−
                if($pista_bd->nombre==$nombre_pista){  // nombre_atraccion está definido arriba
 
−
                    $selected = 'selected';
 
−
                }
 
−
                printf("<option value='%s' %s>%s</option>",$pista_bd->nombre,$selected,$pista_bd->nombre);  // Fixarse que no value ten que ir a clave primaria de táboa
 
−
            }
 
−
        ?>
 
−
    </select>
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo animais/alta_animal.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
// Aunque esta página no hace uso de las constantes, al cargar los campos de animal, esa sí que hace uso de ellas.
 
−
//  Podríamos ponerlo en la página de campos_animal.php, pero necesitaríamos poner require_once ya que la página de editar_animal.php también tiene el include
 
−
require('/var/www/confBD/bd_pistas.php'); 
 
−
?>
 
−
 
 
−
 
 
−
<!DOCTYPE html>
 
−
<html>
 
−
<head>
 
−
  <title>Alta Animal</title>
 
−
</head>
 
−
<body>
 
−
  <h3>ALTA ANIMAL</h3>
 
−
  <form method="POST" action="procesar_alta_animal.php">
 
−
    <?php include 'campos_animal.php'; ?>
 
−
    <div>
 
−
        <input type="submit" value="Dar de Alta" name="btnAltaAnimal">
 
−
    </div>
 
−
  </form>
 
−
</body>
 
−
</html>
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo animais/procesar_alta_animal.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
if (isset($_POST['btnAltaAnimal'])) {
 
−
  try {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
    // Lóxica para dar de alta un novo animal
 
−
    $nombre = $conex->real_escape_string($_POST['nombre']);
 
−
    $tipo = $conex->real_escape_string($_POST['tipo']);
 
−
    $anhos = $conex->real_escape_string($_POST['anhos']);
 
−
    $peso = $conex->real_escape_string($_POST['peso']);
 
−
    $estatura = $conex->real_escape_string($_POST['estatura']);
 
−
    $nombre_atraccion = $conex->real_escape_string($_POST['nombre_atraccion']);
 
−
    $nombre_pista = $conex->real_escape_string($_POST['nombre_pista']);
 
−
 
 
−
 
 
−
    // Comprobamos os campos que poden ser nulos
 
−
    if (empty($anhos)) $anhos = 'null';
 
−
    if (empty($peso)) $peso = 'null';
 
−
    if (empty($estatura)) $estatura = 'null';
 
−
    if (empty($nombre_atraccion)) $nombre_atraccion = 'null';
 
−
    if (empty($nombre_pista)) $nombre_pista = 'null';
 
−
 
 
−
    $consulta = sprintf(INSERTAR_ANIMAL,$nombre,$tipo,$anhos,$peso,$estatura,$nombre_atraccion,$nombre_pista);
 
−
    $consulta = str_replace("'null'","null",$consulta); // En caso de ter valores null estarán desta forma: "'null'" ao ter INSERTAR_ANIMAL o código campo='%s'
 
−
 
 
−
    $conex->query($consulta);
 
−
 
 
−
  } catch (mysqli_sql_exception $e) {
 
−
    die("Erro: " . $e->getMessage());
 
−
  }
 
−
  catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
  }
 
−
  finally{
 
−
    if(!empty($conex)){
 
−
      $conex->close();
 
−
    } 
 
−
  }
 
−
 
 
−
}
 
−
header("Location: consultar_animais.php");
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo animais/editar_animal.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
$conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
$conex->set_charset('utf8mb4');
 
−
$erros = '';
 
−
 
 
−
?>
 
−
 
 
−
<?php
 
−
    /**
 
−
    * htmlEntities non se pode empregar sobre un valor null e como temos campos que poden vir con null fago esta función
 
−
    * Como imos empregala en varios sitio, podería estar definida un arquivo de utilidades.inc-php
 
−
    */
 
−
    function comprobarNull($valor){
 
−
      $dato =  $valor==null ? '' : $valor;
 
−
      return htmlentities($dato);
 
−
    }
 
−
 
 
−
    $consulta = sprintf(CONSULTAR_ANIMAL_POR_NOME,$conex->real_escape_string($_GET['nombre']));
 
−
    $result=$conex->query($consulta);  // Ven na URL da paxina listar_pistas.php
 
−
    if ($result && $result->num_rows>0){
 
−
        $row = mysqli_fetch_assoc($result); // Os datos serán empregados polos inputs do formulario
 
−
        $nombre = comprobarNull($row['nombre']);
 
−
        $tipo = comprobarNull($row['tipo']);
 
−
        $anhos = comprobarNull($row['anhos']);
 
−
        $peso = comprobarNull($row['peso']);
 
−
        $estatura = comprobarNull($row['estatura']);
 
−
        $nombre_atraccion = $row['nombre_atraccion']; // Non aplico htmlentities xa que non vou amosar o dato. Vou comparalo coa lista de atraccións para poñelo como seleccionado.  Se a aplicara, os acentos sería sustituídos por &acute; por exemplo e non coincidiría
 
−
        $nombre_pista = $row['nombre_pista'];  // Non aplico htmlentities xa que non vou amosar o dato. Vou comparalo coa lista de pistas para poñelo como seleccionado. Se a aplicara, os acentos sería sustituídos por &acute; por exemplo e non coincidiría
 
−
 
 
−
        $result->free();
 
−
    }
 
−
    else {
 
−
        $erros = "Non se atopan os datos do animal!!!!";
 
−
    }
 
−
 
 
−
 
 
−
?>
 
−
<!DOCTYPE html>
 
−
<html>
 
−
<head>
 
−
  <title>Modificar Animal</title>
 
−
</head>
 
−
<body> 
 
−
    <h3>EDITAR ANIMAL</h3>
 
−
 
 
−
  <form method="POST" action="procesar_editar_animal.php">
 
−
    <?php include 'campos_animal.php'; ?>
 
−
    <div>
 
−
        <input type="hidden" name="nome_orixinal" value="<?php echo $nombre; ?>">
 
−
        <input type="submit" value="Modificar" name="btnModificarAnimal">
 
−
    </div>
 
−
  </form>
 
−
  <?php
 
−
    if(!empty($erros)){
 
−
        printf("<h3>$erros</h3>");
 
−
    }
 
−
  ?>
 
−
</body>
 
−
</html>
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo animais/procesar_editar_animal.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
if (isset($_POST['btnModificarAnimal'])) {
 
−
  try {
 
−
    require ('/var/www/confBD/bd_pistas.php');
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
 
 
−
    // Lóxica para dar de alta un novo animal
 
−
    $nombre = $conex->real_escape_string($_POST['nombre']);
 
−
    $tipo = $conex->real_escape_string($_POST['tipo']);
 
−
    $anhos = $conex->real_escape_string($_POST['anhos']);
 
−
    $peso = $conex->real_escape_string($_POST['peso']);
 
−
    $estatura = $conex->real_escape_string($_POST['estatura']);
 
−
    $nombre_atraccion = $conex->real_escape_string($_POST['nombre_atraccion']);
 
−
    $nombre_pista = $conex->real_escape_string($_POST['nombre_pista']);
 
−
 
 
−
    $nome_orixinal = $conex->real_escape_string($_POST['nome_orixinal']); // nome_orixinal ven nun campo oculta dende a páxina de editar_animal.php
 
−
 
 
−
    // Comprobamos os campos que poden ser nulos
 
−
    if (empty($anhos)) $anhos = 'null';
 
−
    if (empty($peso)) $peso = 'null';
 
−
    if (empty($estatura)) $estatura = 'null';
 
−
    if (empty($nombre_atraccion)) $nombre_atraccion = 'null';
 
−
    if (empty($nombre_pista)) $nombre_pista = 'null';
 
−
 
 
−
    $consulta = sprintf(ACTUALIZAR_ANIMAL,$nombre,$tipo,$anhos,$peso,$estatura,$nombre_atraccion,$nombre_pista,$nome_orixinal); 
 
−
    $consulta = str_replace("'null'","null",$consulta); // En caso de ter valores null estarán desta forma: "'null'" ao ter INSERTAR_ANIMAL o código campo='%s'
 
−
   
 
−
    $conex->query($consulta);
 
−
 
 
−
 
 
−
  } catch (mysqli_sql_exception $e) {
 
−
    die("Erro: " . $e->getMessage());
 
−
  }
 
−
  catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
  }
 
−
  finally{
 
−
    if(!empty($conex)){
 
−
      $conex->close();
 
−
    } 
 
−
  }
 
−
}
 
−
header("Location: consultar_animais.php");
 
−
 
 
−
?>
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
'''Arquivo animais/procesar_borrar_animal.php'''
 
−
::<syntaxhighlight lang="php" line enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd_pistas.php');
 
−
if (isset($_POST['btnBorrarAnimal'])) {
 
−
  try {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD);
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
    // Lóxica para dar de alta unha nova pista
 
−
    $nombre = $conex->real_escape_string($_POST['nombre']);
 
−
    $consulta = sprintf(BORRAR_ANIMAL,$nombre);
 
−
    $conex->query($consulta);
 
−
 
 
−
  } catch (mysqli_sql_exception $e) {
 
−
    die("Erro: " . $e->getMessage());
 
−
  }
 
−
  catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
  }
 
−
  finally{
 
−
    if(!empty($conex)){
 
−
      $conex->close();
 
−
    } 
 
−
  }
 
−
 
 
−
}
 
−
header("Location: consultar_animais.php");
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
 
 
−
==Exercicios propostos==
 
−
 
 
−
* Se fixéchedes os exercicios anteriores xa tedes unha boa base para facer calquera outro.
 
−
: Podedes coller calquera táboa e facer a xestión da mesma.
 
−
: Fixarse que as relacións N:M temos varias forma de mantelas. Poderíamos ter unha táboa na que se amosan os datos relacionados e depois dous select, un para seleccionar un dos valores da relación N e outro para seleccionar o outro valor da relación M.
 
−
 
 
−
 
 
−
* No caso das operacións de INSERT/UPDATE/DELETE para maior claridade, o código php que procesa a información e a manda á base de datos, está definido nun arquivo diferente a onde está o formulario.
 
−
: Nas operacións de consulta, podedes obtar por facelo no mesmo arquivo onde está o formulario.
 
−
 
 
−
 
 
−
* Outros exercicios propostos (que non sexan de xestión):
 
−
:* Dado o nome e apelidos dun autor a escoller nunha combo (select), cando se seleccione deberán amosarse a lista de títulos que escribira nuha táboa, odeados polo título. En caso de non ter libros debarase amosar o texto Sen libros escritos.
 
−
:* Amosar todos libros con paxinación e asociado a cada libro un radiobutton. Ao premer no radiobutton deberán aparecer os exemplares de dito libro, coa imaxe, prezo e estado.
 
−
:* Sobre unha caixa de texto, escribir o título dun libro (facer con like). Se deben amosar os libros que teñan o patrón indicado xunto có número de exemplares de cada libro. Ao premer no número de exemplares, abrirase outra ventana coa lista de exemplares, o seu estado e imaxe.
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
* Tamén poderíamos partindo dun elemento da relación N, amosar todos os elementos da relación M e seleccionar varios á vez, cun checkbox. Por exemplo, podería amosar a lista de atracciones, seleccionar unha delas e amosar os artistas que traballan nela. Dende esa pantalla podería ir a outra para asociar ou quitar artistas a dita atracción. Aparecería unha lista con todos os artistas, asociado a cada un deles un checkbox, e podería marcar ou desmarcar artistas. Facer iso levaría empregar unha transacción, e [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Transacci.C3.B3ns veremos como facelo máis adiante].
 
−
 
 
−
 
 
−
 
 
−
 
 
−
 
 
  
−
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2023).
+
* 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

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.

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 POST para 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.
Mod BD Prog Modelo Circo.jpg
  • 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, UPDATE e DELETE. O permiso EXECUTE só 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 CIRCO en MySQL 8 e comprobar a estrutura e as restricións das táboas.
  • Crear un usuario específico con permisos mínimos (SELECT, INSERT, UPDATE e DELETE) sobre as táboas necesarias. Non empregar root na 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_errors 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 INSERT, UPDATE e DELETE.
  • As consultas preparadas non deben reconstruírse concatenando datos do formulario; os valores variables enlázanse con parámetros.


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


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 PISTAS e ATRACCIONES;
  • 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.

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


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.php queda 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 hidden poden 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_NULL cando necesitemos ligar explicitamente un valor SQL NULL.
  • Non construiremos manualmente a cadea 'null' nin faremos str_replace() sobre a consulta.
  • Os valores usados para decidir unha opción selected compararanse 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.
  • mysqli queda como ampliación, tamén empregando consultas preparadas.



Comprobación final dos exercicios

Antes de dar por rematada a práctica comprobaremos que:

  1. As listaxes funcionan sen parámetros e as consultas dunha fila empregan prepare() e execute().
  2. O alta valida os campos no servidor, insire mediante POST e trata as claves duplicadas.
  3. A modificación conserva a clave primaria orixinal e permite cambiar o nome da pista ou do animal.
  4. O borrado só se realiza mediante POST e trata posibles conflitos de integridade referencial.
  5. Os campos opcionais baleiros gárdanse como NULL cando corresponde e non como texto 'null'.
  6. As etiquetas, textos e atributos HTML amosan os datos mediante htmlspecialchars().
  7. Non se mostran credenciais nin mensaxes técnicas de excepción no navegador.
  8. 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.