Diferencia entre revisiones de «PHP UD3 OP ALTA»
| Línea 264: | Línea 264: | ||
<br /> | <br /> | ||
| − | ===== Posible Solución: Utilizando orden INSERT ===== | + | ===== Posible Solución: Utilizando orden INSERT orientado a obxectos===== |
* Lembrar que dependendo dos casos, teremos que verificar que o valor a engadir non estea dado de alta previamente... | * Lembrar que dependendo dos casos, teremos que verificar que o valor a engadir non estea dado de alta previamente... | ||
| Línea 336: | Línea 336: | ||
die("Erro mysql: " . $e->getMessage()); | die("Erro mysql: " . $e->getMessage()); | ||
} | } | ||
| − | catch ( | + | catch (Exception $e) { |
die("Erro xeral: " . $e->getMessage()); | die("Erro xeral: " . $e->getMessage()); | ||
} | } | ||
| Línea 345: | Línea 345: | ||
} | } | ||
?> | ?> | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| Línea 363: | Línea 357: | ||
'''Arquivo alta_estado.php''' | '''Arquivo alta_estado.php''' | ||
::<syntaxhighlight lang="java" enclose="div" highlight="" > | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
<!DOCTYPE html> | <!DOCTYPE html> | ||
<html> | <html> | ||
| Línea 444: | Línea 426: | ||
$error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage())); | $error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage())); | ||
} | } | ||
| − | catch ( | + | catch (Exception $e) { |
$error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage())); | $error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage())); | ||
} | } | ||
| Línea 457: | Línea 439: | ||
?> | ?> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | <br /> | ||
| + | ===== Posible Solución: Utilizando orden INSERT empregando PDO===== | ||
| + | |||
| + | * Lembrar que dependendo dos casos, teremos que verificar que o valor a engadir non estea dado de alta previamente... | ||
| + | '''Páxina bd.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | define ('CONSULTAR_ESTADOS','select id,descripcion from estados order by descripcion'); | ||
| + | define('CONSULTAR_ESTADO_NUM',"SELECT count(*) as existe FROM estados WHERE upper(descripcion)=upper(:descripcion)"); | ||
| + | define('INSERIR_ESTADO',"INSERT INTO estados (descripcion) VALUES (:descripcion)"); | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | '''Arquivo consultar_estados.php''' | ||
| + | ::<syntaxhighlight lang="java" 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.php'); | ||
| + | |||
| + | try { | ||
| + | $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8mb4", SERVIDOR, BD); | ||
| + | $conex = new PDO($dsn, USUARIO, PASSWORD); | ||
| + | $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | ||
| + | |||
| + | $erro = ""; // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse. | ||
| + | ?> | ||
| + | |||
| + | <!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 estados</title> | ||
| + | </head> | ||
| + | <body> | ||
| + | <form action="alta_estado.php" method="post"> | ||
| + | <button type="submit">Novo Estado</button> | ||
| + | </form> | ||
| + | <table border="1" style="margin-top:20px"> | ||
| + | <tr> | ||
| + | <th>Estado</th> | ||
| + | </tr> | ||
| + | <?php | ||
| + | $result=$conex->query(CONSULTAR_ESTADOS); // Temos que poñer prepare | ||
| + | if ($result && $result->rowCount() > 0) { | ||
| + | while ($row = $result->fetch(PDO::FETCH_ASSOC)) { | ||
| + | $nombre = htmlentities($row['descripcion']); | ||
| + | echo "<tr>"; | ||
| + | echo "<td>$nombre</td>"; | ||
| + | echo "</tr>"; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | $result = null; | ||
| + | ?> | ||
| + | </table> | ||
| + | |||
| + | |||
| + | <div> | ||
| + | <?php | ||
| + | if(isset($_GET['id'])) printf("<h3>Estado con id %d dado de alta correctamente.</h3>",htmlentities($_GET['id'])); | ||
| + | if(isset($_GET['error'])) printf("<h2>Houbo un erro: %s</h2>",htmlentities($_GET['error'])); | ||
| + | ?> | ||
| + | </div> | ||
| + | |||
| + | </body> | ||
| + | </html> | ||
| + | <?php | ||
| + | } // Pecha o try do principio da páxina | ||
| + | catch (PDOException $e) { | ||
| + | die("Erro PDO: " . $e->getMessage()); | ||
| + | } catch (Exception $e) { | ||
| + | die("Erro xeral: " . $e->getMessage()); | ||
| + | } finally { | ||
| + | $conex = null; // Pechar a conexión | ||
| + | } | ||
| + | ?> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | |||
| + | '''Arquivo campos_estado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <label for="nombre">Descricción:</label> | ||
| + | <input type="text" maxlength="100" id="descripcion" name="descripcion" required><br> | ||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | '''Arquivo alta_estado.php''' | ||
| + | ::<syntaxhighlight lang="java" enclose="div" highlight="" > | ||
| + | <!DOCTYPE html> | ||
| + | <html> | ||
| + | <head> | ||
| + | <title>Alta Estado</title> | ||
| + | </head> | ||
| + | <body> | ||
| + | <h3>ALTA ESTADO</h3> | ||
| + | <form method="POST" action="procesar_alta_estado.php"> | ||
| + | <?php include 'campos_estado.php'; ?> | ||
| + | <div style='margin-top:20px'> | ||
| + | <input type="submit" value="ALTA" name="btnOperacion"> | ||
| + | </div> | ||
| + | </form> | ||
| + | </body> | ||
| + | </html> | ||
| + | |||
| + | </syntaxhighlight> | ||
| + | |||
| + | |||
| + | '''Arquivo procesar_alta_estado.php''' | ||
| + | ::<syntaxhighlight lang="java" 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.php'); | ||
| + | |||
| + | $error=""; // Variable que a usaremos para amosar os posibles erros que poidan darse | ||
| + | $msg=''; // Variable empregada para enviar o novo id xerado | ||
| + | try { | ||
| + | $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8mb4",SERVIDOR,BD); | ||
| + | $conex = new PDO($dsn, USUARIO, PASSWORD); | ||
| + | $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | ||
| + | |||
| + | // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion | ||
| + | if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){ | ||
| + | if ((strlen($_POST['descripcion'])<4) || (strlen($_POST['descripcion'])>100)){ | ||
| + | $error = sprintf("error=%s", "O númnero de caracteres do estado non pode ter máis de 100 caracteres nin menos de 4..."); | ||
| + | } | ||
| + | else { | ||
| + | // Eliminamos caracteres non permitidos | ||
| + | $novoEstado = filter_var($_POST['descripcion'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); | ||
| + | |||
| + | $stmt=$conex->prepare(CONSULTAR_ESTADO_NUM); // Temos que poñer prepare | ||
| + | $stmt->bindValue(':descripcion',$novoEstado,PDO::PARAM_STR); | ||
| + | $result = $stmt->execute(); | ||
| + | |||
| + | if($result){ | ||
| + | while ($fila = $stmt->fetch(PDO::FETCH_ASSOC)) { | ||
| + | if ($fila['existe']>0) { | ||
| + | $error = sprintf("error=%s", "Ese estado xa existe."); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | $stmt = null; | ||
| + | |||
| + | if($error==''){ | ||
| + | $stmt=$conex->prepare(INSERIR_ESTADO); // Temos que poñer prepare | ||
| + | $stmt->bindValue(':descripcion',$novoEstado,PDO::PARAM_STR); | ||
| + | $result = $stmt->execute(); | ||
| + | |||
| + | if ($result){ | ||
| + | $msg = sprintf("id=%d", $conex->lastInsertId()); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | } // Fin da operacion de ALTA | ||
| + | |||
| + | } // Pecha o try da parte superior da páxina. Lembrar que aos usuarios non se lle deben amosar os erros. Isto está posto a nivel informativo para os programadores | ||
| + | catch (PDOException $e) { | ||
| + | $error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage())); | ||
| + | } | ||
| + | catch (Exception $e) { | ||
| + | $error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage())); | ||
| + | } | ||
| + | finally{ | ||
| + | if(!empty($conex)){ | ||
| + | $conex = null; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | $url = sprintf("consultar_estados.php?%s&%s",$msg,$error); | ||
| + | header("Location:" . $url); | ||
| + | |||
| + | ?> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Revisión del 12:00 4 nov 2025
Sumario
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.
- Para dar de alta novos rexistros na base de datos temos que facer uso da instrución SQL INSERT.
- O proceso é moi parecido ao que fixemos coas consultas, coa diferenza que no caso dunha instrución INSERT non temos rexistros de volta.
- Soamente debemos comprobar se a inserción a fixo correctamente, comprobando se a chamado ao método query devolve false.
- Comentar que tamén pode sernos útil a instrución INSERT IGNORE. Esta instrución non vai emitir ningún erro se intentamos inserir unha clave duplicada nunha táboa. Loxicamente a operación non se fai.
- Nestes exemplos, vaise empregar a versión PHP 8.1 ou superior que vai lanzar excepcións en caso de erro como xa vimos no control erros ao abrir a conexión á base de datos.
- Podedes consultar como controlar os erros nunha versión PHP anterior consultando a sección de consultas xa que o procedemento é o mesmo cambiando a orde SQL a executar.
Orientado a obxectos
- O proceso para realizar unha inserción é o seguinte:
- Abrimos unha conexión á base de datos.
- Construímos a orde SQL: INSERT INTO TABOA_1 (col1,col2) VALUES (valor1,'valor2')
- Neste caso a TABOA_1 está constituída por dúas columnas, a primeira numérica e a segunda de tipo cadea.
- Executamos a orde e comprobamos que a operación foi correcta:
- Pechamos a conexión á base de datos.
try { $conex = new mysqli('SERVIDOR', 'USUARIO', 'PASSWORD', 'BD'); $conex->set_charset('utf8mb4'); $consulta = "INSERT INTO TABOA_1 (col1,col2) VALUES (valor1,'valor2')"; // Envía un dato numérico e un dato cadea. Nos empregaremos %d e %s nunha constante previamente definida e despois sprintf $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(); } } }
- Fixarse como a consulta a gardamos nunha variable para non ter problemas coas comiñas na chamada á función query.
- Cando o rexistro foi dado de alta correctamente podemos comprobar o número de rexistros afectados (no caso dun único rexistro sempre vai devolver 1, pero tamén se podería aplicar a un insert multifila).
.................. $result=$conex->query($query); if ($result) { echo "Rexistro dado de alta correctamente"; echo "Num. rexistros afectados:" . $conex->affected_rows; } $conex->close(); // Pechamos a conexión
Empregando PDO
- O proceso para realizar unha inserción é o seguinte:
- Abrimos unha conexión á base de datos.
- Construímos a orde SQL có paso de dous parámetros: INSERT INTO TABOA_1 (col1,col2) VALUES (:valor1,:valor2)
- Facemos o execute enviando os datos aos parámetros.
- Comprobamos que a operación foi correcta:
- Pechamos a conexión á base de datos.
<?php try { // Abrir conexión coa base de datos $conex = new PDO( "mysql:host=SERVIDOR;dbname=BD;charset=utf8mb4", 'USUARIO', 'PASSWORD' ); $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Construír a orde SQL // Supoñamos TABOA_1(col1 numérico, col2 cadea) define('INSERIR_TABOA_1', 'INSERT INTO TABOA_1 (col1, col2) VALUES (:col1, :col2)'); // Preparar a sentenza $stmt = $conex->prepare(INSERIR_TABOA_1); // Valores a inserir $valor1 = 123; $valor2 = "Texto de proba"; // Executar a consulta con parámetros $stmt->execute([':col1' => $valor1, ':col2' => $valor2]); echo "Inserción correcta. ID xerado: " . $conex->lastInsertId(); } catch (PDOException $e) { die("Erro PDO: " . $e->getMessage()); } catch (Exception $e) { die("Erro xeral: " . $e->getMessage()); } finally { // Pechar conexión if (!empty($stmt)) $stmt = null; if (!empty($conex)) $conex = null; } ?>
- Cando o rexistro foi dado de alta correctamente podemos comprobar o número de rexistros afectados (no caso dun único rexistro sempre vai devolver 1, pero tamén se podería aplicar a un insert multifila).
.................. $stmt->execute([':col1' => $valor1, ':col2' => $valor2]); // Comprobar se se inseriu correctamente $numRexistros = $stmt->rowCount(); // Normalmente 1 nun insert if ($numRexistros > 0) { echo "Rexistro dado de alta correctamente<br>"; echo "Num. rexistros afectados: " . $numRexistros; } else { echo "Non se inseriu ningún rexistro."; } .............
Obtendo o valor dun campo autonumérico
- Normalmente cando deseñamos unha base de datos, certos campos que son claves os definimos como autonuméricos.
- Ao facelo, cando engadimos unha nova fila a un táboa, xérase un novo valor e en certas ocasións necesitamos obter dito valor xa que o podemos utilizar para gardar outros datos noutras táboas que o necesitan por estar relacionadas.
- Non vale a solución de engadir un dato e inmediatamente facer un 'select' buscando o valor máis alto, xa que non entorno multiusuario, dito valor non ten porqué ser o último engadido.
- Necesitamos utilizar a función mysqli::$insert_id].
- Dita función devolve o id autoxerado que se utilizou na última operación.
- Por exemplo, imaxinemos que temos a táboa TABLE_1 con dúas columnas, col1 int primary key autogenerate e col2 varchar(100).
- Cando executamos a orde: INSERT INTO TABLE_1 (col2) VALUES ('valor1')
- O proceso é o seguinte:
- Abrimos a conexión á base de datos.
- Executamos a orde SQL que engade unha nova fila.
- Orientado a obxectos
$result=$conex->query("INSERT INTO TABLE_1 (col2) VALUES ('valor')"); if ($result){ // Operación correcta printf ("Novo rexistro có id %d.\n", $conex->insert_id); } ........... $conex->close(); // Pechamos a conexión
- Empregando PDO
$stmt = $conex->prepare('INSERT INTO TABOA_1 (col2) VALUES (:col2)'); $stmt->execute([':col2' => 'valor2']); $numRexistros = $stmt->rowCount(); if ($numRexistros > 0) { echo "Operación correcta<br>"; echo "Novo rexistro có id " . $conex->lastInsertId() . "<br>"; }
Alta utilizando procedementos almacenados
- O proceso de alta é o mesmo que o visto nas consultas, pero utilizando a orde SQL INSERT dentro do procedemento.
- Se estamos a utilizar procedementos almacenados, lembrar que en caso de erro '2014 - Commands out of sync; you can't run this command now', teredes que engadir debemos de engadir a seguinte liña despois de chamar a un procedemento almacenado:
if ($conex->multi_query($proc)){ // Neste caso supoño que o procedemento de alta devolve un conxunto de filas a maiores de facer o INSERT. Non é o normal. $result = $conex->store_result(); if ($result && ($result->num_rows>0)){ while($fila=$result->fetch_assoc()){ // Procesamos cada fila } $result->free(); } while($conex->next_result() && $conex->store_result());
- IMPORTANTE: Se o procedemento de alta non vai devolver datos (cun select) podemos facer uso da orde $conex->query($proc) sendo $proc='CALL NOME_PROC(parámetros)'
- Nota: Hai que ter unha correspondencia entre o tipo de dato e tamaño do parámetro do procedemento almacenado e a columna da táboa onde imos a inserir o dato.
- Así, se a táboa TABOA_1 ten de columnas col1 int, col2 varchar(100)
- Se defino un procedemento almacenado para dar de alta unha nova fila: CREATE PROCEDURE ALTA_FILA_TABOA_1(_param1 int, _param2 varchar(100))
- Indicar que dentro dun procedemento almacenado imos poder ter sentenzas:
- E outras de control de fluxo como podedes ver neste enlace.
Caso práctico
- Imos facer o mantemento da táboa ESTADOS.
- Imos crear unha páxina de nome consultar_estados.php. Dita páxina amosará o listado de estados nunha táboa e terá un botón cun formulario na parte superior para dar de alta un novo estado.
- Dito formulario apuntará a unha páxina de nome alta_estado.php onde se amosará un formulario cun campo de texto para engadir a descricción do estado (lembrar que o id é autonumérico).
- O action da páxina 'alta_estado.php' chamará a unha páxina de nome 'procesar_alta_estado.php' o cal fará a operación de INSERT e fará unha redirección á paxina 'consultar_estados' enviando na URL o novo id xerado e unha mensaxe que poderá ser de información (alta dada de alta correctamente) ou de erro.
- A páxina alta_estado fará uso dun include de nome campos_estado.php. En dita páxina (campos_estado.php) estará definido a caixa de texto do formulario que debe ser obrigatorio introducir un dato e o tamaño máximo será de 100 caracteres e o mínimo de 4.
- No exemplo emprégase a función strtoupper para converter o texto a maiúsculas antes de envialo á base de datos.
- Cando traballades con tipos de datos cadeas será algo que teredes que ter en conta. Vai depender se no formulario de entrada de datos permitides ou non as minúsculas, ides necesitar empregar esta opción.
- Outro aspecto importante é que ten que haber unha correspondencia de tipo e tamaño entre o campo da táboa onde imos facer a operación (neste caso a táboa ESTADOS, campo descripcion, que é un varchar(100)) e o parámetro que imos definir no procedemento almacenado.
- O envío das mensaxes de erro e id a través da URL non é o máis correcto, pero neste punto do curso aínda no vimos o emprego de variables de sesión que sería a forma correcto de facelo.
Posible Solución: Utilizando orden INSERT orientado a obxectos
- Lembrar que dependendo dos casos, teremos que verificar que o valor a engadir non estea dado de alta previamente...
Páxina bd.php
define ('CONSULTAR_ESTADOS','select id,descripcion from estados order by descripcion'); define('CONSULTAR_ESTADO_NUM',"SELECT count(*) as existe FROM estados WHERE upper(descripcion)=upper('%s')"); define('INSERIR_ESTADO',"INSERT INTO estados (descripcion) VALUES ('%s')");
Arquivo consultar_estados.php
<?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.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 estados</title> </head> <body> <form action="alta_estado.php" method="post"> <button type="submit">Novo Estado</button> </form> <table border="1" style="margin-top:20px"> <tr> <th>Estado</th> </tr> <?php $resultado = $conex->query(CONSULTAR_ESTADOS); while($fila = $resultado->fetch_assoc()) { $nombre = htmlentities($fila['descripcion']); echo "<tr>"; echo "<td>$nombre</td>"; echo "</tr>"; } $resultado->free(); ?> </table> <div> <?php if(isset($_GET['id'])) printf("<h3>Estado con id %d dado de alta correctamente.</h3>",htmlentities($_GET['id'])); if(isset($_GET['error'])) printf("<h2>Houbo un erro: %s</h2>",htmlentities($_GET['error'])); ?> </div> </body> </html> <?php } // Pecha o try da parte superior da páxina catch (mysqli_sql_exception $e) { die("Erro mysql: " . $e->getMessage()); } catch (Exception $e) { die("Erro xeral: " . $e->getMessage()); } finally{ if(!empty($conex)){ $conex->close(); } } ?>
Arquivo campos_estado.php
<label for="nombre">Descricción:</label> <input type="text" maxlength="100" id="descripcion" name="descripcion" required><br>
Arquivo alta_estado.php
<!DOCTYPE html> <html> <head> <title>Alta Estado</title> </head> <body> <h3>ALTA ESTADO</h3> <form method="POST" action="procesar_alta_estado.php"> <?php include 'campos_estado.php'; ?> <div style='margin-top:20px'> <input type="submit" value="ALTA" name="btnOperacion"> </div> </form> </body> </html>
Arquivo procesar_alta_estado.php
<?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.php'); $error=""; // Variable que a usaremos para amosar os posibles erros que poidan darse $msg=''; // Variable empregada para enviar o novo id xerado try { $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); $conex->set_charset('utf8mb4'); // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){ if ((strlen($_POST['descripcion'])<4) || (strlen($_POST['descripcion'])>100)){ $error = sprintf("error=%s", "O númnero de caracteres do estado non pode ter máis de 100 caracteres nin menos de 4..."); } else { // Eliminamos caracteres non permitidos $novoEstado = filter_var($_POST['descripcion'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); // Escapamos as comillas simples e dobres $novoEstado = $conex->real_escape_string(strtoupper($novoEstado)); $query=sprintf(CONSULTAR_ESTADO_NUM,$novoEstado); $result = $conex->query($query); if ($result && ($result->num_rows>0)){ $fila=$result->fetch_assoc(); if ($fila['existe']>0) { $error = sprintf("error=%s", "Ese estado xa existe."); } $result->free(); } if($error==''){ $query=sprintf(INSERIR_ESTADO,$novoEstado); $result = $conex->query($query); if ($result){ $msg = sprintf("id=%d", $conex->insert_id); } } } } // Fin da operacion de ALTA } // Pecha o try da parte superior da páxina. Lembrar que aos usuarios non se lle deben amosar os erros. Isto está posto a nivel informativo para os programadores catch (mysqli_sql_exception $e) { $error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage())); } catch (Exception $e) { $error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage())); } finally{ if(!empty($conex)){ $conex->close(); } } $url = sprintf("consultar_estados.php?%s&%s",$msg,$error); header("Location:" . $url); ?>
Posible Solución: Utilizando orden INSERT empregando PDO
- Lembrar que dependendo dos casos, teremos que verificar que o valor a engadir non estea dado de alta previamente...
Páxina bd.php
define ('CONSULTAR_ESTADOS','select id,descripcion from estados order by descripcion'); define('CONSULTAR_ESTADO_NUM',"SELECT count(*) as existe FROM estados WHERE upper(descripcion)=upper(:descripcion)"); define('INSERIR_ESTADO',"INSERT INTO estados (descripcion) VALUES (:descripcion)");
Arquivo consultar_estados.php
<?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.php'); try { $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8mb4", SERVIDOR, BD); $conex = new PDO($dsn, USUARIO, PASSWORD); $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $erro = ""; // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse. ?> <!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 estados</title> </head> <body> <form action="alta_estado.php" method="post"> <button type="submit">Novo Estado</button> </form> <table border="1" style="margin-top:20px"> <tr> <th>Estado</th> </tr> <?php $result=$conex->query(CONSULTAR_ESTADOS); // Temos que poñer prepare if ($result && $result->rowCount() > 0) { while ($row = $result->fetch(PDO::FETCH_ASSOC)) { $nombre = htmlentities($row['descripcion']); echo "<tr>"; echo "<td>$nombre</td>"; echo "</tr>"; } } $result = null; ?> </table> <div> <?php if(isset($_GET['id'])) printf("<h3>Estado con id %d dado de alta correctamente.</h3>",htmlentities($_GET['id'])); if(isset($_GET['error'])) printf("<h2>Houbo un erro: %s</h2>",htmlentities($_GET['error'])); ?> </div> </body> </html> <?php } // Pecha o try do principio da páxina catch (PDOException $e) { die("Erro PDO: " . $e->getMessage()); } catch (Exception $e) { die("Erro xeral: " . $e->getMessage()); } finally { $conex = null; // Pechar a conexión } ?>
Arquivo campos_estado.php
<label for="nombre">Descricción:</label> <input type="text" maxlength="100" id="descripcion" name="descripcion" required><br>
Arquivo alta_estado.php
<!DOCTYPE html> <html> <head> <title>Alta Estado</title> </head> <body> <h3>ALTA ESTADO</h3> <form method="POST" action="procesar_alta_estado.php"> <?php include 'campos_estado.php'; ?> <div style='margin-top:20px'> <input type="submit" value="ALTA" name="btnOperacion"> </div> </form> </body> </html>
Arquivo procesar_alta_estado.php
<?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.php'); $error=""; // Variable que a usaremos para amosar os posibles erros que poidan darse $msg=''; // Variable empregada para enviar o novo id xerado try { $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8mb4",SERVIDOR,BD); $conex = new PDO($dsn, USUARIO, PASSWORD); $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){ if ((strlen($_POST['descripcion'])<4) || (strlen($_POST['descripcion'])>100)){ $error = sprintf("error=%s", "O númnero de caracteres do estado non pode ter máis de 100 caracteres nin menos de 4..."); } else { // Eliminamos caracteres non permitidos $novoEstado = filter_var($_POST['descripcion'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); $stmt=$conex->prepare(CONSULTAR_ESTADO_NUM); // Temos que poñer prepare $stmt->bindValue(':descripcion',$novoEstado,PDO::PARAM_STR); $result = $stmt->execute(); if($result){ while ($fila = $stmt->fetch(PDO::FETCH_ASSOC)) { if ($fila['existe']>0) { $error = sprintf("error=%s", "Ese estado xa existe."); } } } $stmt = null; if($error==''){ $stmt=$conex->prepare(INSERIR_ESTADO); // Temos que poñer prepare $stmt->bindValue(':descripcion',$novoEstado,PDO::PARAM_STR); $result = $stmt->execute(); if ($result){ $msg = sprintf("id=%d", $conex->lastInsertId()); } } } } // Fin da operacion de ALTA } // Pecha o try da parte superior da páxina. Lembrar que aos usuarios non se lle deben amosar os erros. Isto está posto a nivel informativo para os programadores catch (PDOException $e) { $error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage())); } catch (Exception $e) { $error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage())); } finally{ if(!empty($conex)){ $conex = null; } } $url = sprintf("consultar_estados.php?%s&%s",$msg,$error); header("Location:" . $url); ?>
Posible Solución: Utilizando procedementos almacenados
- A diferenza do caso anterior:
- O id xerado terá que devolvelo o procedemento, mediante SELECT, un parámetro de SAÍDA ou un RETURN se estamos a utilizar unha función.
- Teremos que utilizar a función last_insert_id() para obter o id xerado dende o procedemento de Mysql.
- A comprobación de se o dato existe o facemos dentro do procedemento.
Procedemento almacenado creado en Mysql
DELIMITER $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_ESTADO`(p_descripcion varchar(100)) etiqueta:BEGIN -- Asociamos unha etiqueta ao código do procedemento para que poidamos facer algo parecido a un exit declare existe int; SELECT count(*) INTO existe FROM estados WHERE upper(descripcion)=upper(p_descripcion); IF existe=1 THEN begin SELECT -1 as valor_devolto; leave etiqueta; -- Para que saia do procedemento end; END IF; -- Fin do IF INSERT INTO estados (descripcion) VALUES (p_descripcion); SELECT last_insert_id() as valor_devolto; END DELIMITER ;
- Liña 4: Utilizamos unha etiqueta para que poidamos utilizar a orde LEAVE xa que Mysql non ten outra forma de poder saír dun procedemento almacenado.
- Neste caso en concreto quizais podería ser más aconsellable utilizar unha función, xa que estamos a devolver un único dato (neste caso -1 ou a id xerada). Desta maneira, poñendo un return xa sairiamos da función.
- Liña 22: Utilizamos a funciuón last_insert_id() para obter o último id xerado da inserción.
- Comentar que dentro dos xestores de base de datos, nos procedementos/funcións podemos empregar as consultas directamente como expresións, por exemplo, no caso anterior:
DELIMITER $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_ESTADO`(p_descripcion varchar(100)) etiqueta:BEGIN -- Asociamos unha etiqueta ao código do procedemento para que poidamos facer algo parecido a un exit IF (SELECT count(*) FROM estados WHERE upper(descripcion)=upper(p_descripcion))>0 THEN begin SELECT -1 as valor_devolto; leave etiqueta; -- Para que saia do procedemento end; END IF; -- Fin do IF INSERT INTO estados (descripcion) VALUES (p_descripcion); SELECT last_insert_id() as valor_devolto; END$$
Páxina bd.php
define('PROCEDEMENTO_INSERIR_ESTADO',"INSERIR_ESTADO('%s')");
Páxina procesar_alta_estado.php
<?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.php'); $error=""; // Variable que a usaremos para amosar os posibles erros que poidan darse $msg=''; // Variable empregada para enviar o novo id xerado try { $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); $conex->set_charset('utf8mb4'); // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){ if ((strlen($_POST['descripcion'])<4) || (strlen($_POST['descripcion'])>100)){ $error = sprintf("error=%s", "A lonxitude do estado non pode ter máis de 100 caracteres nin menos de 4..."); } else { // Eliminamos caracteres non permitidos $novoEstado = filter_var($_POST['descripcion'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); // Escapamos as comillas simples e dobres $novoEstado = $conex->real_escape_string(strtoupper($novoEstado)); $query=sprintf(CONSULTAR_ESTADO_NUM,$novoEstado); $result = $conex->query($query); if ($result && ($result->num_rows>0)){ $fila=$result->fetch_assoc(); if ($fila['existe']>0) { $error = sprintf("error=%s", "Ese estado xa existe."); } $result->free(); } if($error==''){ $proc=sprintf(PROCEDEMENTO_INSERIR_ESTADO,$novoEstado); $proc = sprintf("CALL %s",$proc); if ($conex->multi_query($proc)){ $result = $conex->store_result(); if ($result && ($result->num_rows>0)){ $fila=$result->fetch_assoc(); switch($fila['valor_devolto']){ case -1: $error = sprintf("error=%s", "Ese valor xa está dado de alta na base de datos..."); break; default: $msg = sprintf("id=%d", $fila['valor_devolto']); } $result->free(); } else{ // Sempre ten que devolver algo como temos implementado o proc. $error = sprintf("error=%s", "Houbo un erro na chamada á base de datos"); } } } } } // Fin da operacion de ALTA } // Pecha o try da parte superior da páxina. Lembrar que aos usuarios non se lle deben amosar os erros. Isto está posto a nivel informativo para os programadores catch (mysqli_sql_exception $e) { $error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage())); } catch (Expcetion $e) { $error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage())); } finally{ if(!empty($conex)){ $conex->close(); } } $url = sprintf("consultar_estados.php?%s&%s",$msg,$error); header("Location:" . $url); ?>
- Neste caso é necesario utilizar a opción multi_query xa que recuperamos un dato que devolve o procedemento.
Como tratar aos caracteres 'estraños'
- Con caracteres 'estraños' queremos dicir caracteres fora do abecedario e caracteres numéricos, coma por exemplo: ', ,\n,....
- Aquí debemos diferenciar dous casos.
- Cando queremos gardar caracteres estraños dentro da base de datos.
- Cando queremos impedir que o usuario poida utilizar ese tipo de caracteres.
Necesitamos gardar caracteres 'estraños' na base de datos
- Neste caso, teremos que 'codificar' e 'descodificar' os datos.
- Codificaremos os datos cando os gardemos (ou modifiquemos) coa orde SQL INSERT e UPDATE.
- Decodificaremos: Pode non ser necesario, dependerá do dato e o que queiramos facer con él.
- As funcións que podemos empregar para isto son:
- Función htmlentities: Codifica os caracteres en entidades html. Por exemplo, '<' o codifica como '<'. Fixarse nas 'opcións' xa que por defecto codifica as comiñas dobres pero non as simples.
- Función html_entity_decode: Realiza o proceso inverso ao anterior.
- Por exemplo, imaxinade que o usuario engade nunha caixa de texto un código HTML que queredes gardar na base de datos, pero que o navegador non debe 'interpretar'. Por exemplo a cadea: <script>alert('ola);</script>
- Isto podería ser interpretado como un ataque como xa vimos anteriormente. Codificando a cadea coa función htmlentities isto non é posible.
- Cando amosemos o resultado de aplicar a función, non deberemos decodificalo, xa que o que queremos é que o navegador 'amose' a cadea, non que 'interprete' o código e execute o código javascript.
IMPORTANTE: Lembrar que para utilizar esta solución, todos os datos (ou os que nos interesen que poidan ter este tipo de caracteres) deben ser codificados.
- Se un usuario fai unha busca sobre algún destes campos, teremos que codificar tamén a cadea escrita polo usuario.
- Outra forma de 'codificar' caracteres 'estraños' é empregando a función mysqli::real_escape_string.
- Esta función, aplicada a unha cadea de caracteres, 'escapa' os seguintes caracteres: NUL (ASCII 0), \n, \r, \, ', ", e Control-Z. Isto quere dicir que antepón a ditos caracteres o carácter \ e isto fai que o Mysql interprete o seguinte carácter como o que é.
Nota:Deberemos aplicar sempre esta función para impedir a inxección de código se non aplicamos outra función que 'codifique' as comiñas simples, antes de enviar os datos á base de datos.
- Vexamos un exemplo coa seguinte cadea:
- $valor = "O'Really";
- Queremos facer a seguinte consulta: INSERT INTO AUTORES (nome) VALUES ('$valor')
- Se substituímos $valor polo que vale, vemos que temos un problema coas comiñas simples e daría un erro de sintaxe SQL.
- Para solucionalo,podemos aplicar a función 'real_escape_string' a $valor da forma:
<?php $mysqli = new mysqli("localhost", "usuario", "contrasinal", "bd"); if (mysqli_connect_errno()) { printf("Erro na conexión: %s\n", mysqli_connect_error()); exit(); } $valor = "O'Really"; /* esta consulta faia debido a que non escapa $cidade */ if (!$mysqli->query("INSERT INTO AUTORES (nome) VALUES ('$valor')")) { printf("Erro: %s\n", $mysqli->sqlstate); } $valor = $mysqli->real_escape_string($valor); /* esta consulta con $valor escapada funcionará */ if (!$mysqli->query("INSERT INTO AUTORES (nome) VALUES ('$valor')")) { printf("%d fila inserida.\n", $mysqli->affected_rows); } $mysqli->close(); ?>
-- Ángel D. Fernández González -- (2023).