Diferencia entre revisiones de «PHP UD3 OP BAIXA»

De MediaWiki
Ir a la navegación Ir a la búsqueda
Línea 2: Línea 2:
  
  
−
 
+
* '''<u>Requerimentos previos:</u>'''  
−
* '''<u>Requerimentos previos:</u>''' Ter creada a base de datos indicada [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Creando_as_t.C3.A1boas_en_Mysql nunha sección anterior] e ter gardados os parámetros de conexión nun arquivo separado [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Gardando_os_datos_de_conexi.C3.B3n_nun_lugar_seguro como foi indicado nunha sección anterior]
+
:* Ter creada a base de datos indicada [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Creando_as_t.C3.A1boas_en_Mysql nunha sección anterior]
−
 
+
:* Ter gardados os parámetros de conexión nun arquivo separado [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Gardando_os_datos_de_conexi.C3.B3n_nun_lugar_seguro 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 [https://wiki.cifprodolfoucha.es/index.php?title=PHP_UD3_OP_CONSULTA na sección de consultas] e na [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Control_de_errores sección de conexión].
  
  

Revisión del 06:51 25 oct 2023

Introducción

  • Requerimentos previos:


  • 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.




  • 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&oacute;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&aacute;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&Oacute;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 &eacute; v&aacute;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&aacute; 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).