Diferencia entre revisiones de «PHP UD3 OP ALTA»

De MediaWiki
Ir a la navegación Ir a la búsqueda
Línea 138: Línea 138:
 
<br />
 
<br />
 
==== Caso práctico: Obtendo o valor dun campo autonumérico ====
 
==== Caso práctico: Obtendo o valor dun campo autonumérico ====
 +
* Imos aplicar isto ao noso exemplo:
 +
:* 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.
  
−
* Imos aplicar isto ao noso exemplo.
 
−
: Imos crear unha páxina de nome '''Xestionar_Estados.php'''. Dita páxina terá un formulario onde indicaremos cun opcións de radio-button se queremos dar de alta, baixa ou modificar un estado da táboa ESTADOS.
 
−
: Nos imos implementar a opción de 'Alta' de novos estados.
 
  
−
* Ao seleccionar dita opción, aparecerá un novo formulario no que introduciremos nunha caixa de texto o novo estado.
+
* No exemplo emprégase a [https://www.php.net/manual/es/function.strtoupper.php función strtoupper] para converter o texto a maiúsculas antes de envialo á base de datos.
−
: Ao premer o botón de alta, chamaremos a un procedemento almacenado de nome 'INSERIR_ESTADO()' pasando como parámetro o novo estado.
+
: 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.
−
: Dito procedemento verificará que o estado xa non estea dado de alta previamente. Nese caso devolverá en forma de 'SELECT' o valor -1. En caso de que todo vaia ben, devolverá o id xerado utilizando un SELECT.
 
  
 +
* Outro aspecto importante é que <u>ten que haber unha correspondencia de tipo e tamaño</u> 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.
  
−
:<u>Nota:</u> Fixarse que se non utilizáramos procedementos almacenados e o fixeramos con consultas directamente, teríamos que facer un consulta para comprobar se o estado estaba dado de alta previamente (por exemplo con: select count(*) as existe from ESTADOS where upper(descripcion)=upper('valor_enviad') e outra sentenza para engadir o novo estado en caso de que non exista.
 
−
 
−
: No exemplo anterior emprégase a [http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_upper función UPPER] para converter o texto a maiúsculas.
 
−
: 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 copón.
 
−
 
−
 
−
* Outro aspecto importante é que <u>ten que haber unha correspondencia de tipo e tamaño</u> 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 [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Sesi%C3%B3ns variables de sesión] que sería a forma correcto de facelo.
  
  
 +
<br />
 
===== Posible Solución: Utilizando orden INSERT =====
 
===== Posible Solución: Utilizando orden INSERT =====
  
Línea 163: Línea 160:
 
'''Páxina bd.inc.php'''
 
'''Páxina bd.inc.php'''
 
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
−
define('CONSULTAR_ESTADO_NUM',"SELECT count(*) as existe FROM ESTADOS WHERE upper(descripcion)=upper('%s')");
+
define ('CONSULTAR_ESTADOS','select id,descripcion from estados order by descripcion');
−
define('INSERIR_ESTADO',"INSERT INTO ESTADOS (descripcion) VALUES ('%s')");
+
define('CONSULTAR_ESTADO_NUM',"SELECT count(*) as existe FROM estados WHERE upper(descripcion)=upper('%s')");
 +
define('INSERIR_ESTADO',"INSERT INTO estados (descripcion) VALUES ('%s')");
 
</syntaxhighlight>
 
</syntaxhighlight>
  
  
  
−
'''Páxina Xestionar_Estados.php'''
+
'''Arquivo consultar_estados.php'''
 
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 
<?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.inc.php');
 
require ('/var/www/confBD/bd.inc.php');
−
//error_reporting(0);
 
  
−
$conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
+
try {
−
$conex->set_charset('utf8');
+
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);  
 +
    $conex->set_charset('utf8mb4');
  
−
if ($conex->connect_error){
 
−
    die('Erro de Conexi&oacute;n (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
 
−
}
 
−
$erro="";  // Variable que a usaremos para amosar os posibles erros que poidan darse
 
 
?>
 
?>
−
 
−
 
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Línea 191: Línea 187:
 
     <head>
 
     <head>
 
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
−
         <title>Xestionar t&aacute;boa Estados BD</title>
+
         <title>Xestión de estados</title>
 
     </head>
 
     </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
  
−
    <body>
+
            $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>
  
  
−
         <!-- Esta parte poderíase facer con javascript -->
+
         <div>
−
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
+
            <?php
−
            <h2>Escolle a operación a facer sobre a táboa ESTADOS</h2>
+
                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']));
−
            <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='ALTA') ? "checked='checked'" : ""; ?>
+
            ?>
−
                 onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='ALTA'>ALTA</input>
+
        </div>  
−
            <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='BAIXA') ? "checked='checked'" : "";  ?>
 
−
                onclick='javascript:this.form-submit()'  type='radio' name="rbtnOperacion" value='BAIXA'>BAIXA</input>
 
−
            <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='MODIFICACION') ? "checked='checked'" : ""; ?>
 
−
                onclick='javascript:this.form-submit()'  type='radio' name="rbtnOperacion" value='MODIFICACION'>MODIFICACI&Oacute;N</input>
 
−
        <?php 
 
 
          
 
          
−
        if (!empty($_POST['rbtnOperacion'])){ // Premimos unha opción => Xeramos o formulario
+
    </body>   
−
            switch ($_POST['rbtnOperacion']){
+
</html>
−
                case 'ALTA':
+
<?php
−
                    printf("<div>Nome do novo estado:");
+
}  // Pecha o try da parte superior da páxina
−
                    printf("<input name='txtEstado' type='text' size='50' maxlength='100' /></div>");
+
catch (mysqli_sql_exception $e) {
−
                    printf("<div><input name='btnOperacion' type='submit' value='ALTA' />");
+
    die("Erro mysql: " . $e->getMessage());
−
                    printf("<input type='reset' value='BORRAR' /></div>");
+
}
−
                    break;
+
catch (Expcetion $e) {
 +
    die("Erro xeral: " . $e->getMessage());
 +
}
 +
finally{
 +
  if(!empty($conex)){
 +
      $conex->close();
 +
  }
 +
}
 +
?>
 +
</syntaxhighlight>
 +
 
 +
 
 +
'''Arquivo alta_estado.php'''
 +
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
 
 +
</syntaxhighlight>
 +
 
 +
 
 +
'''Arquivo campos_estado.php'''
 +
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
 +
<label for="nombre">Nome:</label>
 +
<input type="text" maxlength="100" id="nombre" name="nombre" required><br>
 +
</syntaxhighlight>
 +
 
 +
 
 +
'''Arquivo alta_estado.php'''
 +
::<syntaxhighlight lang="java" 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.inc.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>
 +
 
 +
</syntaxhighlight>
 +
 
 +
 
 +
'''Arquivo procesar_alta_estado.php'''
 +
::<syntaxhighlight lang="java" 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.inc.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['nombre'])<4) || (strlen($_POST['nombre'])>100)){
 +
            $error = sprintf("error=%s", "A lonxitude do estado non pode ter máis de 100 caracteres nin menos de 5...");
 +
        }
 +
        else {
 +
            // Eliminamos caracteres non permitidos
 +
            $novoEstado = filter_var($_POST['nombre'],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();
 
             }
 
             }
−
        }
 
  
−
        // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
+
            if($error==''){
−
        if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){
+
                 $query=sprintf(INSERIR_ESTADO,$novoEstado);
−
            if (strlen($_POST['txtEstado'])>100){
 
−
                $erro = ">A lonxitude da cadea non pode ter máis de 100 caracteres...";
 
−
            }
 
−
            else {
 
−
                // Eliminamos caracteres non permitidos
 
−
                $novoEstado = filter_var($_POST['txtEstado'],FILTER_SANITIZE_STRING);
 
−
                // Escapamos as comillas simples e dobres
 
−
                $novoEstado = $conex->real_escape_string($novoEstado);
 
−
               
 
−
                 $query=sprintf(CONSULTAR_ESTADO_NUM,$novoEstado);
 
 
                 $result = $conex->query($query);
 
                 $result = $conex->query($query);
−
                 if ($result && ($result->num_rows>0)){
+
                 if ($result){
−
                     $fila=$result->fetch_assoc();
+
                     $msg = 'id=' . $conex->insert_id;
−
                    if ($fila['existe']>0) {
 
−
                        $erro="Ese valor xa foi engadido...";
 
−
                    }
 
−
                    $result->free();
 
 
                 }
 
                 }
 +
            }
 +
        } 
 +
           
 +
    }  // 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();
 +
    }
 +
}
  
−
                if($erro==''){
+
$url = sprintf("consultar_estados.php?%s&%s",$msg,$error);
−
                    $query=sprintf(INSERIR_ESTADO,$novoEstado);
+
header("Location:" . $url);
−
                    $result = $conex->query($query);
+
 
−
                    if ($result){
+
?>      
−
                        printf("<h2>Valor dado de alta correctamente</h2>");
 
−
                        printf("<h3>Nova clave %d</h3>",$conex->insert_id);
 
−
                    }
 
−
                    else{  // Sempre ten que devolver algo como temos implementado o proc.
 
−
                        $erro = 'Houbo un erro na chamada á base de datos';
 
−
                    }
 
−
                }
 
−
            } 
 
−
               
 
−
        }  // Fin da operacion de ALTA
 
−
       
 
−
        ?>      
 
  
−
        </form>
 
−
       
 
−
       
 
−
        <?php       
 
−
        if ($erro!='') {
 
−
            printf("<h3>%s</h3>",$erro);
 
−
        }
 
−
        $conex->close();  // Pechamos a conexión
 
−
       
 
−
        ?>       
 
−
       
 
−
       
 
−
    </body>   
 
−
</html>
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
 +
 +
 +
 +
<br />
 
===== Posible Solución: Utilizando procedementos almacenados =====
 
===== Posible Solución: Utilizando procedementos almacenados =====
  

Revisión del 19:00 24 oct 2023

Introducción

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.


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.


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


 1   try {
 2 
 3     $conex = new mysqli('SERVIDOR', 'USUARIO', 'PASSWORD', 'BD');
 4     $conex->set_charset('utf8mb4');
 5 
 6 
 7     $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
 8     $conex->query($consulta);
 9 
10   } catch (mysqli_sql_exception $e) {
11     die("Erro: " . $e->getMessage());
12   }
13   catch (Expcetion $e) {
14     die("Erro xeral: " . $e->getMessage());
15   }
16   finally{
17     if(!empty($conex)){
18       $conex->close();
19     }  
20   }
21 }
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).
1 ..................
2 $result=$conex->query($query);
3 if ($result) {
4   echo "Rexistro dado de alta correctamente";
5   echo "Num. rexistros afectados:" . $conex->affected_rows;
6 }
7 $conex->close();  // Pechamos a conexión



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:
1                     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.
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());


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



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.
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.
1 $result=$conex->query("INSERT INTO TABLE_1 (col2) VALUES ('valor')");
2 if ($result){
3   // Operación correcta
4   printf ("Novo rexistro có id %d.\n", $conex->insert_id);
5 }
  • Pechamos a conexión.
1 $conex->close();  // Pechamos a conexión



Caso práctico: Obtendo o valor dun campo autonumérico

  • Imos aplicar isto ao noso exemplo:
  • 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
  • Lembrar que dependendo dos casos, teremos que verificar que o valor a engadir non estea dado de alta previamente...

Páxina bd.inc.php

1 define ('CONSULTAR_ESTADOS','select id,descripcion from estados order by descripcion');
2 define('CONSULTAR_ESTADO_NUM',"SELECT count(*) as existe FROM estados WHERE upper(descripcion)=upper('%s')");
3 define('INSERIR_ESTADO',"INSERT INTO estados (descripcion) VALUES ('%s')");


Arquivo consultar_estados.php

 1 <?php
 2 /* Lembrar quitar isto ao probar o scripts sen erros */
 3 ini_set('display_errors', 1);
 4 ini_set('display_startup_errors', 1);
 5 error_reporting(E_ALL);
 6 
 7 require ('/var/www/confBD/bd.inc.php');
 8 
 9 try {
10     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   
11     $conex->set_charset('utf8mb4');
12 
13 ?>
14 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
15     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
16 <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
17     <head>
18         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
19         <title>Xestión de estados</title>
20     </head>
21     <body>
22         <form action="alta_estado.php" method="post">
23             <button type="submit">Novo Estado</button>
24         </form>
25         <table border="1" style="margin-top:20px">
26             <tr>
27                 <th>Estado</th>
28             </tr>
29         <?php
30 
31             $resultado = $conex->query(CONSULTAR_ESTADOS);
32 
33             while($fila = $resultado->fetch_assoc()) {
34                 $nombre = htmlentities($fila['descripcion']);
35                 echo "<tr>";
36                 echo "<td>$nombre</td>";
37                 echo "</tr>";
38             }
39 
40             $resultado->free();
41         ?>    
42         </table>
43 
44 
45         <div>
46             <?php
47                 if(isset($_GET['id'])) printf("<h3>Estado con id %d dado de alta correctamente.</h3>",htmlentities($_GET['id']));
48                 if(isset($_GET['error'])) printf("<h2>Houbo un erro: %s</h2>",htmlentities($_GET['error']));
49             ?>
50         </div>    
51         
52     </body>    
53 </html>
54 <?php
55 }   // Pecha o try da parte superior da páxina
56 catch (mysqli_sql_exception $e) {
57     die("Erro mysql: " . $e->getMessage());
58 }
59 catch (Expcetion $e) {
60     die("Erro xeral: " . $e->getMessage());
61 }
62 finally{
63   if(!empty($conex)){
64       $conex->close();
65   }
66 }
67 ?>


Arquivo alta_estado.php


Arquivo campos_estado.php

1 <label for="nombre">Nome:</label>
2 <input type="text" maxlength="100" id="nombre" name="nombre" required><br>


Arquivo alta_estado.php

 1 <?php 
 2 /* Lembrar quitar isto ao probar o scripts sen erros */
 3 ini_set('display_errors', 1);
 4 ini_set('display_startup_errors', 1);
 5 error_reporting(E_ALL);
 6 
 7 // Aunque esta página no hace uso de las constantes, al cargar los campos de animal, esa sí que hace uso de ellas.
 8 //  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
 9 require('/var/www/confBD/bd.inc.php');  
10 ?>
11 
12 
13 <!DOCTYPE html>
14 <html>
15 <head>
16   <title>Alta Estado</title>
17 </head>
18 <body>
19   <h3>ALTA ESTADO</h3>
20   <form method="POST" action="procesar_alta_estado.php">
21     <?php include 'campos_estado.php'; ?>
22     <div style='margin-top:20px'>
23         <input type="submit" value="ALTA" name="btnOperacion">
24     </div>
25   </form>
26 </body>
27 </html>


Arquivo procesar_alta_estado.php

 1 <?php
 2 /* Lembrar quitar isto ao probar o scripts sen erros */
 3 ini_set('display_errors', 1);
 4 ini_set('display_startup_errors', 1);
 5 error_reporting(E_ALL);
 6 
 7 require ('/var/www/confBD/bd.inc.php');
 8 
 9 $error="";   // Variable que a usaremos para amosar os posibles erros que poidan darse
10 $msg='';    // Variable empregada para enviar o novo id xerado 
11 try {
12     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   
13     $conex->set_charset('utf8mb4');
14     // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
15     if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){  
16         if ((strlen($_POST['nombre'])<4) || (strlen($_POST['nombre'])>100)){
17             $error = sprintf("error=%s", "A lonxitude do estado non pode ter máis de 100 caracteres nin menos de 5...");
18         }
19         else {
20             // Eliminamos caracteres non permitidos
21             $novoEstado = filter_var($_POST['nombre'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
22             // Escapamos as comillas simples e dobres
23             $novoEstado = $conex->real_escape_string(strtoupper($novoEstado));
24             $query=sprintf(CONSULTAR_ESTADO_NUM,$novoEstado);
25             $result = $conex->query($query);
26             if ($result && ($result->num_rows>0)){
27                 $fila=$result->fetch_assoc();
28                 if ($fila['existe']>0) {
29                     $error = sprintf("error=%s", "Ese estado xa existe.");
30                 }
31                 $result->free();
32             }
33 
34             if($error==''){
35                 $query=sprintf(INSERIR_ESTADO,$novoEstado);
36                 $result = $conex->query($query);
37                 if ($result){
38                     $msg = 'id=' .  $conex->insert_id;
39                 }
40             }
41         }  
42             
43     }  // Fin da operacion de ALTA
44         
45 }   // 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
46 catch (mysqli_sql_exception $e) {
47     $error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage()));
48 }
49 catch (Expcetion $e) {
50     $error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage()));
51 }
52 finally{
53     if(!empty($conex)){
54         $conex->close();
55     }
56 }
57 
58 $url = sprintf("consultar_estados.php?%s&%s",$msg,$error);
59 header("Location:" . $url);
60 
61 ?>




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

 1 DELIMITER $$
 2 
 3 CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_ESTADO`(_descripcion varchar(100))
 4 etiqueta:BEGIN  -- Asociamos unha etiqueta ao código do procedemento para que poidamos facer algo parecido a un exit
 5     declare existe int;
 6 	
 7 	SELECT count(*) 
 8 	INTO existe
 9 	FROM ESTADOS
10 	WHERE upper(descripcion)=upper(_descripcion);
11 
12 	IF existe=1 THEN
13 		begin
14 			SELECT -1 as valor_devolto;
15 			leave etiqueta; -- Para que saia do procedemento
16 		end;
17 	END IF;  -- Fin do IF
18 
19 	INSERT INTO ESTADOS (descripcion)
20 	VALUES (_descripcion);
21 
22 	SELECT last_insert_id() as valor_devolto;
23 
24 END
  • 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 16: 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:
 1 DELIMITER $$
 2 
 3 CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_ESTADO`(_descripcion varchar(100))
 4 etiqueta:BEGIN  -- Asociamos unha etiqueta ao código do procedemento para que poidamos facer algo parecido a un exit
 5 
 6 	IF 	(SELECT count(*) FROM ESTADOS WHERE upper(descripcion)=upper(_descripcion))>0 THEN
 7 		begin
 8 			SELECT -1 as valor_devolto;
 9 			leave etiqueta; -- Para que saia do procedemento
10 		end;
11 	END IF;  -- Fin do IF
12 
13 	INSERT INTO ESTADOS (descripcion)
14 	VALUES (_descripcion);
15 
16 	SELECT last_insert_id() as valor_devolto;
17 
18 END



Páxina bd.inc.php

1 define('PROCEDEMENTO_INSERIR_ESTADO',"INSERIR_ESTADO('%s')");


Páxina 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        
 29             <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='ALTA') ? "checked='checked'" : "";  ?>
 30                 onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='ALTA'>ALTA</input>
 31             <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='BAIXA') ? "checked='checked'" : "";  ?>
 32                 onclick='javascript:this.form-submit()'  type='radio' name="rbtnOperacion" value='BAIXA'>BAIXA</input>
 33             <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='MODIFICACION') ? "checked='checked'" : "";  ?>
 34                 onclick='javascript:this.form-submit()'  type='radio' name="rbtnOperacion" value='MODIFICACION'>MODIFICACI&Oacute;N</input>
 35         <?php   
 36         
 37         if (!empty($_POST['rbtnOperacion'])){  // Premimos unha opción => Xeramos o formulario
 38             switch ($_POST['rbtnOperacion']){
 39                 case 'ALTA':
 40                     printf("<div>Nome do novo estado:");
 41                     printf("<input name='txtEstado' type='text' size='50' maxlength='100' /></div>");
 42                     printf("<div><input name='btnOperacion' type='submit' value='ALTA' />");
 43                     printf("<input type='reset' value='BORRAR' /></div>");
 44                     break;
 45             }
 46         }
 47 
 48 
 49         // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
 50         if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){  
 51             if (strlen($_POST['txtEstado'])>100){
 52                 $erro = ">A lonxitude da cadea non pode ter máis de 100 caracteres...";
 53             }
 54             else {
 55                 // Eliminamos caracteres non permitidos
 56                 $novoEstado = filter_var($_POST['txtEstado'],FILTER_SANITIZE_STRING);
 57                 // Escapamos as comillas simples e dobres
 58                 $novoEstado = $conex->real_escape_string($novoEstado);
 59                 $proc=sprintf(PROCEDEMENTO_INSERIR_ESTADO,$novoEstado);
 60                 $proc = sprintf("CALL %s",$proc);
 61                 if ($conex->multi_query($proc)){
 62                     $result = $conex->store_result();
 63                     if ($result && ($result->num_rows>0)){
 64                         $fila=$result->fetch_assoc();
 65                         switch($fila['valor_devolto']){
 66                             case -1:
 67                                 $erro = "Ese valor xa est&aacute; dado de alta na base de datos...";
 68                                 break;
 69                             default:
 70                                 printf("<h2>Valor dado de alta correctamente</h2>");
 71                                 printf("<h3>Nova clave %d</h3>",$fila['valor_devolto']);
 72                         }
 73                         $result->free();
 74                     }
 75                     else{   // Sempre ten que devolver algo como temos implementado o proc.
 76                         $erro = 'Houbo un erro na chamada á base de datos';
 77                     }
 78                 }
 79                 else{
 80                     $erro = 'Houbo un erro na chamada á base de datos';
 81                 }
 82             }  
 83                 
 84         }  // Fin da operacion de ALTA
 85         
 86         ?>        
 87         </form>
 88         
 89         
 90         <?php        
 91         if ($erro!='') {
 92             printf("<h3>%s</h3>",$erro);
 93         }
 94         $conex->close();  // Pechamos a conexión
 95         
 96         ?>        
 97         
 98         
 99     </body>    
100 </html>


  • 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 '&lt'. 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.



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:
 1 <?php
 2 $mysqli = new mysqli("localhost", "usuario", "contrasinal", "bd");
 3 
 4 if (mysqli_connect_errno()) {
 5     printf("Erro na conexión: %s\n", mysqli_connect_error());
 6     exit();
 7 }
 8 
 9 
10 $valor = "O'Really";
11 
12 /* esta consulta faia debido a que non escapa $cidade */
13 if (!$mysqli->query("INSERT INTO AUTORES (nome) VALUES ('$valor')")) {
14     printf("Erro: %s\n", $mysqli->sqlstate);
15 }
16 
17 $valor = $mysqli->real_escape_string($valor);
18 
19 /* esta consulta con $valor escapada funcionará */
20 if (!$mysqli->query("INSERT INTO AUTORES (nome) VALUES ('$valor')")) {
21     printf("%d fila inserida.\n", $mysqli->affected_rows);
22 }
23 
24 $mysqli->close();
25 ?>

Non queremos que o usuario poida utilizar este tipo de caracteres







-- Ángel D. Fernández González -- (2023).