PHP UD3 OP BAIXA
Revisión del 06:51 25 oct 2023 de Angelfg (discusión | contribuciones)
Introducción
- Requerimentos previos:
- Ter creada a base de datos indicada nunha sección anterior
- Ter gardados os parámetros de conexión nun arquivo separado como foi indicado nunha sección anterior
- Estes exercicios están baseados nunha versión de PHP 8.1 ou superior onde as ordes PHP para manexar a base de datos lanzan expcecións. Se tedes unha vesión anterior, teredes que facer o indicado na sección de consultas e na sección de conexión.
- NOTA IMPORTANTE: Vimos en seccións anteriores que cando empregmos datos dun formulario, existen unhas técnicas para evitar posibles ataques. Todos os exercicios que veñen a continuación empregan ditas técnicas. Existe un tipo de ataque que non se ten en conta ao non ter chegado á sección de emprego de variables de sesións. En dita sección se explica o que temos que facer. É importante que vos acordedes de aplicar a técnica descrita cando vexades os contidos.
- O proceso de baixa faise por medio da orde sql DELETE.
- Podemos eliminar unha fila ou un conxunto de filas en base a uns criterios (parte WHERE da cláusula DELETE)
- Ao igual que fixemos anteriormente, podemos borrar directamente en PHP facendo uso da orde DELETE ou chamar a un procedemento almacenado, enviando os datos necesarios e executando a sentenza.
- IMPORTANTE: Temos que ter en conta que nas bases de datos relacionais, dar de baixa un rexistro pode implicar borrar rexistros relacionados.
- Cando creamos as táboas indicamos as relacións entre elas por medio de claves foráneas, nas que indicamos como queremos que se comporten cando se produce un borrado ou unha modificación. Máis información neste enlace.
- Por exemplo, na táboa LIBROS_AUTORES:
1 CREATE TABLE `LIBROS_AUTORES` ( 2 `libro_id` int(11) NOT NULL, 3 `autor_id` int(11) NOT NULL, 4 PRIMARY KEY (`libro_id`,`autor_id`), 5 KEY `fk_LIBROS_AUTORES_AUTORES_idx` (`autor_id`), 6 CONSTRAINT `fk_LIBROS_AUTORES_AUTORES` FOREIGN KEY (`autor_id`) REFERENCES `AUTORES` (`id_autor`) ON DELETE NO ACTION ON UPDATE CASCADE, 7 CONSTRAINT `fk_LIBROS_AUTORES_LIBROS` FOREIGN KEY (`libro_id`) REFERENCES `LIBROS` (`id_libro`) ON DELETE NO ACTION ON UPDATE CASCADE 8 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;
- No exemplo estamos a indicar que en caso de borrar unha fila de táboa AUTORES, non se vai permitir se existen filas na táboa LIBROS_AUTORES.
- Se actualizamos a clave primaria da táboa AUTORES, actualizará a clave na táboa AUTORES_LIBROS (parte ON UPDATE CASCADE).
- Polo tanto, será tarefa do programador comprobar se os datos están sendo utilizados noutras táboas e avisar en caso de que o usuario intente borralos ou ben permitir borrados en cascada ou ben capturar o erro que dará o mysql en caso de intentar borrar datos relacionados e informar ao usuario do problema.
Caso Práctico: Baixa
- Imos implementar esta opción na páxina Xestionar_Estados.php.
- Cargaremos nunha <select> todos os estados e premendo o botón 'Baixa' daremos de baixa o estado seleccionado.
- Faremos uso dun procedemento almacenado de nome BORRAR_ESTADO que terá como parámetro o id do estado a borrar e fará a sentenza sql DELETE.
- Faremos uso do procedemento almacenado 'CONSULTAR_ESTADOS' que devolverá nun select o id e descrición dos estados para cargar na lista.
- Nesta solución non comprobamos se xa existen datos relacionados con ese estado antes de borralos e usaremos como base o código feito na solución Xestionar_Estados_Proc.php feito anteriormente.
- Se estamos a utilizar procedementos almacenados, lembrar que en caso de erro '2014 - Commands out of sync; you can't run this command now', debemos engadir a seguinte liña despois de chamar a un procedemento almacenado:
1 if ($conex->multi_query($proc)){ 2 $result = $conex->store_result(); 3 if ($result && ($result->num_rows>0)){ 4 while($fila=$result->fetch_assoc()){ 5 // Procesamos cada fila 6 } 7 $result->free(); 8 } 9 while($conex->next_result() && $conex->store_result());
- A xestión do erro en caso de querer borrar un rexistro relacionado a imos facer na páxina PHP comprobando que o valor numérico do erro sexa o 1451, que se corresponde co erro 'Cannot delete or update a parent row: a foreign key constraint fails'.
- Lembrar que dito control poderíamos facelo no propio procedemento almacenado capturando a excepción ou comprobando cun select previo se existe o rexistro relacionado.
- Nota: Lembrar que nesta solución estamos a utilizar a chamada a multi_query pero se podería utilizar a chamada a query no caso de que o procedemento non devolva resultados (teña un select que queiramos recoller en PHP).
Posible solución Caso Práctico: Baixa
Procedemento almacenado en Mysql: CONSULTAR_ESTADOS
1 DELIMITER $$ 2 3 CREATE DEFINER=`root`@`localhost` PROCEDURE `CONSULTAR_ESTADOS`() 4 BEGIN 5 SELECT id_estado,descripcion 6 FROM ESTADOS 7 ORDER BY descripcion; 8 9 END
Procedemento almacenado en Mysql: BORRAR_ESTADO
1 DELIMITER $$ 2 3 CREATE DEFINER=`root`@`localhost` PROCEDURE `BORRAR_ESTADO`(_id_estado int) 4 BEGIN 5 6 DELETE FROM ESTADOS 7 WHERE id_estado = _id_estado; 8 9 END
Arquivo bd.inc.php
1 define ('PROCEDEMENTO_BORRAR_ESTADO',"BORRAR_ESTADO(%d)"); 2 define ('PROCEDEMENTO_CONSULTAR_ESTADOS',"CONSULTAR_ESTADOS()");
Arquivo Xestionar_Estados_Proc.php
1 <?php 2 3 require ('/var/www/confBD/bd.inc.php'); 4 //error_reporting(0); 5 6 $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD); 7 $conex->set_charset('utf8'); 8 9 if ($conex->connect_error){ 10 die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error()); 11 } 12 $erro=""; // Variable que a usaremos para amosar os posibles erros que poidan darse 13 ?> 14 15 16 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 17 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 18 <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es"> 19 <head> 20 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 21 <title>Xestionar táboa Estados BD</title> 22 </head> 23 24 <body> 25 <!-- Esta parte poderíase facer con javascript --> 26 <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'> 27 <h2>Escolle a operación a facer sobre a táboa ESTADOS</h2> 28 <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='ALTA') ? "checked='checked'" : ""; ?> 29 onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='ALTA'>ALTA</input> 30 <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='BAIXA') ? "checked='checked'" : ""; ?> 31 onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='BAIXA'>BAIXA</input> 32 <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='MODIFICACION') ? "checked='checked'" : ""; ?> 33 onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='MODIFICACION'>MODIFICACIÓN</input> 34 <?php 35 36 // Operación BAIXA => TEN QUE IR ANTES XA QUE DESPOIS CARGAMOS A LISTA COS ESTADOS!!! 37 if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='BAIXA'){ 38 $id_estado = $_POST['lstEstados']; 39 if (!filter_var($id_estado,FILTER_VALIDATE_INT)){ 40 $error='<h4>O estado indicado non é válido...</h4>'; 41 } 42 else { 43 $proc = sprintf(PROCEDEMENTO_BORRAR_ESTADO,$conex->mysql_real_escape($id_estado)); 44 $proc = sprintf("CALL %s",$proc); 45 if ($conex->multi_query($proc)){ 46 printf("<h4>Num. rexistros borrados:%d</h4>", $conex->affected_rows); 47 echo "<script>alert('Rexistro dado de baixa correctamente');</script>"; 48 while($conex->next_result() && $conex->store_result()); 49 } 50 else{ 51 echo "<h5>Falló CALL: (" . $conex->errno . ") " . $conex->error . "</h5>"; 52 if ($conex->errno==1451){ // Clave foránea relacionada 53 echo "<script>alert('O rexistro non se pode dar de baixa. Está sendo utilizado por outras táboas.');</script>"; 54 } 55 } 56 } 57 } // Fin da operación de BAIXA 58 59 60 // CARGAMOS AS DIFERENTES OPCIONS DE FORMULARIO EN BASE A OPERACION ESCOLLIDA DOS RADIOBUTTON: ALTA-BAIXA-MODIFICACION 61 if (!empty($_POST['rbtnOperacion'])){ // Premimos unha opción => Xeramos o formulario 62 switch ($_POST['rbtnOperacion']){ 63 case 'ALTA': 64 printf("<div>Nome do novo estado:"); 65 printf("<input name='txtEstado' type='text' size='50' maxlength='100' /></div>"); 66 printf("<div><input name='btnOperacion' type='submit' value='ALTA' />"); 67 printf("<input type='reset' value='BORRAR' /></div>"); 68 break; 69 case 'BAIXA': 70 printf("<div>Lista de estados:"); 71 $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_ESTADOS); 72 printf("<select name='lstEstados'>"); 73 if ($conex->multi_query($proc)){ 74 $result = $conex->store_result(); 75 if ($result && ($result->num_rows>0)){ 76 while($fila=$result->fetch_assoc()){ 77 printf("<option value='%d'>%s</option>",$fila['id_estado'],$fila['descripcion']); 78 } 79 $result->free(); 80 } 81 while($conex->next_result() && $conex->store_result()); 82 } 83 printf("</select>"); 84 printf("</div>"); 85 printf("<div><input name='btnOperacion' type='submit' value='BAIXA' />"); 86 break; 87 } 88 } 89 ?> 90 </form> 91 92 <?php 93 // Xestionamos a operacion do segundo formulario => Alta 94 if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){ 95 if (strlen($_POST['txtEstado'])>100){ 96 $erro = ">A lonxitude da cadea non pode ter máis de 100 caracteres..."; 97 } 98 else { 99 // Eliminamos caracteres non permitidos 100 $novoEstado = filter_var($_POST['txtEstado'],FILTER_SANITIZE_STRING); 101 // Escapamos as comillas simples e dobres 102 $novoEstado = $conex->real_escape_string($novoEstado); 103 $proc=sprintf(PROCEDEMENTO_INSERIR_ESTADO,$novoEstado); 104 $proc = sprintf("CALL %s",$proc); 105 if ($conex->multi_query($proc)){ 106 $result = $conex->store_result(); 107 if ($result && ($result->num_rows>0)){ 108 $fila=$result->fetch_assoc(); 109 switch($fila['valor_devolto']){ 110 case -1: 111 $erro = "Ese valor xa está dado de alta na base de datos..."; 112 break; 113 default: 114 printf("<h2>Valor dado de alta correctamente</h2>"); 115 printf("<h3>Nova clave %d</h3>",$fila['valor_devolto']); 116 117 break; 118 } 119 $result->free(); 120 } 121 else{ // Sempre ten que devolver algo como temos implementado o proc. 122 $erro = 'Houbo un erro na chamada á base de datos'; 123 } 124 while($conex->next_result() && $conex->store_result()); 125 } 126 else{ 127 $erro = 'Houbo un erro na chamada á base de datos'; 128 } 129 } 130 131 } // Fin da operacion de ALTA 132 133 134 135 ?> 136 <?php 137 if ($erro!='') { 138 printf("<h3>%s</h3>",$erro); 139 } 140 $conex->close(); // Pechamos a conexión 141 142 ?> 143 144 145 </body> 146 </html>
-- Ángel D. Fernández González -- (2023).