Diferencia entre revisiones de «PHP UD3 TRANSACCIONS»

De MediaWiki
Ir a la navegación Ir a la búsqueda
Línea 78: Línea 78:
 
* Para iniciar unha transacción temos dúas opcións:
 
* Para iniciar unha transacción temos dúas opcións:
  
−
:* Inhabilitar as transaccións implícitas, coa [http://php.net/manual/es/mysqli.autocommit.php función autocommit]:
+
:* VERSION ANTIGA: Inhabilitar as transaccións implícitas, coa [http://php.net/manual/es/mysqli.autocommit.php función autocommit]:
 
::* Versión procedimental:
 
::* Versión procedimental:
−
::::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="2" >
 
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
 
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
 
mysqli_autocommit($conex,false);
 
mysqli_autocommit($conex,false);
Línea 86: Línea 86:
  
 
::* Versión obxectual:
 
::* Versión obxectual:
−
::::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="2" >
 
$conex= new mysqli(HOST, USER, PASSWORD, BD);
 
$conex= new mysqli(HOST, USER, PASSWORD, BD);
 
$conex->autocommit(false);
 
$conex->autocommit(false);
 
</syntaxhighlight>  
 
</syntaxhighlight>  
 +
  
 
:: Desta forma as operacións que vaian a continuación desta orde non se farán efectivas ata atopar un commit.
 
:: Desta forma as operacións que vaian a continuación desta orde non se farán efectivas ata atopar un commit.
  
−
:* Iniciar unha transacción coa [http://php.net/manual/es/mysqli.begin-transaction.php función mysqli::begin_transaction].  
+
 
 +
<br />
 +
:* VERSION ACTUAL: Iniciar unha transacción coa [http://php.net/manual/es/mysqli.begin-transaction.php función mysqli::begin_transaction].  
 
:: '''NECESITAMOS QUE A VERSIÓN PHP SEXA 5.5 OU SUPERIOR'''.
 
:: '''NECESITAMOS QUE A VERSIÓN PHP SEXA 5.5 OU SUPERIOR'''.
  
 
::* Versión procedimental:
 
::* Versión procedimental:
−
::::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="2" >
 
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
 
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
 
mysqli_begin_transaction($conex,MYSQLI_TRANS_START_READ_ONLY);
 
mysqli_begin_transaction($conex,MYSQLI_TRANS_START_READ_ONLY);
Línea 103: Línea 106:
  
 
::* Versión obxectual:
 
::* Versión obxectual:
−
::::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="2" >
 
$conex= new mysqli(HOST, USER, PASSWORD, BD);
 
$conex= new mysqli(HOST, USER, PASSWORD, BD);
 
$conex->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);
 
$conex->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);
 
</syntaxhighlight>  
 
</syntaxhighlight>  
 +
 +
 +
::* Versión PDO:
 +
::::<syntaxhighlight lang="java" enclose="div" highlight="4 >
 +
$conex = new PDO("mysql:host=" . HOST . ";dbname=" . BD . ";charset=utf8mb4", USER, PASSWORD);
 +
 +
// Desactivar o modo autocommit
 +
$conex->beginTransaction();
 +
</syntaxhighlight>
 +
 +
  
  
Línea 117: Línea 131:
  
 
::* Versión procedimental:
 
::* Versión procedimental:
−
::::<syntaxhighlight lang="java" line enclose="div" highlight="7" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="7" >
 
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
 
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
 
mysqli_begin_transaction($conex,MYSQLI_TRANS_START_READ_ONLY);
 
mysqli_begin_transaction($conex,MYSQLI_TRANS_START_READ_ONLY);
Línea 130: Línea 144:
  
 
::* Versión obxectual:
 
::* Versión obxectual:
−
::::<syntaxhighlight lang="java" line enclose="div" highlight="7" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="7" >
 
$conex= new mysqli(HOST, USER, PASSWORD, BD);
 
$conex= new mysqli(HOST, USER, PASSWORD, BD);
 
$conex->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);
 
$conex->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);
Línea 143: Línea 157:
  
  
−
* Exemplo (obxectual) empregando unha versión de PHP 8.1 onde as ordes contra a base de datos poden provocar unha excepción:
+
::* PDO:
−
:::<syntaxhighlight lang="java" line enclose="div" highlight="5,6,11,,19,23" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="7-9" >
 +
$conex = new PDO("mysql:host=" . HOST . ";dbname=" . BD . ";charset=utf8mb4", USER, PASSWORD);
 +
$conex->beginTransaction();
 +
 
 +
.........
 +
OPERACIÓN
 +
Si erro
 +
    if ($conex->inTransaction()) {
 +
        $conex->rollBack();
 +
    }
 +
    die('Houbo un erro...');
 +
 
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
 
 +
* '''Exemplo obxectual''' empregando unha versión de PHP 8.1 onde as ordes contra a base de datos poden provocar unha excepción:
 +
:::<syntaxhighlight lang="java" enclose="div" highlight="5,6,11,,19,23" >
 
try {
 
try {
 
     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,DB);  // Constantes definidas nun arquivo fora do sitio web
 
     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,DB);  // Constantes definidas nun arquivo fora do sitio web
Línea 175: Línea 207:
 
   }
 
   }
 
}
 
}
 +
 +
</syntaxhighlight>
 +
 +
 +
* '''Exemplo PDO''' empregando unha versión de PHP 8.1 onde as ordes contra a base de datos poden provocar unha excepción:
 +
:::<syntaxhighlight lang="java" enclose="div" highlight="4,9,26-28,32-34,38" >
 +
<?php
 +
try {
 +
    // Crear conexión PDO
 +
    $conex = new PDO("mysql:host=" . "SERVIDOR" . ";dbname=" . "DB" . ";charset=utf8mb4","USUARIO","PASSWORD");
 +
    $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 +
 +
 +
    // Comezamos a transacción
 +
    $conex->beginTransaction();
 +
 +
    // Primeira inserción
 +
    $stmt = $conex->query("INSERT INTO TABLE_1 (col2) VALUES ('valor')");
 +
    if ($stmt) {
 +
        printf("Novo rexistro có id %d.<br>", $conex->lastInsertId());
 +
    }
 +
 +
    // Segunda inserción
 +
    $stmt = $conex->query("INSERT INTO TABLE_2 (col2) VALUES ('valor')");
 +
    if ($stmt) {
 +
        printf("Novo rexistro có id %d.<br>", $conex->lastInsertId());
 +
    }
 +
 +
}
 +
catch (PDOException $e) {
 +
    // Reverter se hai unha transacción activa
 +
    if (!empty($conex) && $conex->inTransaction()) {
 +
        $conex->rollBack();
 +
    }
 +
    die("Erro PDO: " . htmlentities($e->getMessage()));
 +
}
 +
catch (Exception $e) {
 +
    if (!empty($conex) && $conex->inTransaction()) {
 +
        $conex->rollBack();
 +
    }
 +
    die("Erro xeral: " . htmlentities($e->getMessage()));
 +
}
 +
finally {
 +
    $conex = null; // Pechar conexión (liberar recursos)
 +
}
 +
?>
 +
  
 
</syntaxhighlight>  
 
</syntaxhighlight>  
Línea 182: Línea 261:
 
* Unha vez rematadas as operacións facemos o '''commit''' coa [http://php.net/manual/es/mysqli.commit.php función commit()]:
 
* Unha vez rematadas as operacións facemos o '''commit''' coa [http://php.net/manual/es/mysqli.commit.php función commit()]:
 
::* Versión procedimental:
 
::* Versión procedimental:
−
::::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
mysqli_commit($conex);
 
mysqli_commit($conex);
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
 
::* Versión obxectual:
 
::* Versión obxectual:
−
::::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
$conex->commit();
 
$conex->commit();
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
 +
::* Versión PDO:
 +
::::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
$conex->commit();
 +
</syntaxhighlight>
  
  
  
 
* Se establecimos autocommit(false) deberemos volvelo a establecer en True.
 
* Se establecimos autocommit(false) deberemos volvelo a establecer en True.
−
:::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
:::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
$conex->autocommit(true);
 
$conex->autocommit(true);
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
  
−
* Ao rematar todas as operacións sobre a base de datos pecharemos a conexión.
+
* Ao rematar todas as operacións sobre a base de datos pecharemos a conexión na versión procedimental ou poñemos a null na versión PDO.
−
:::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
:::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
$conex->close();
 
$conex->close();
 
</syntaxhighlight>
 
</syntaxhighlight>
Línea 209: Línea 292:
 
<br />
 
<br />
 
O exemplo anterior de forma completa (empregando unha versión de PHP 8.1 onde as ordes contra a base de datos poden provocar unha excepción:):
 
O exemplo anterior de forma completa (empregando unha versión de PHP 8.1 onde as ordes contra a base de datos poden provocar unha excepción:):
−
:::<syntaxhighlight lang="java" line enclose="div" highlight="16,21,26,32" >
+
'''VERSION OBXECTUAL'''
 +
:::<syntaxhighlight lang="java" enclose="div" highlight="16,21,26,32" >
 
try {
 
try {
 
     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,DB);  // Constantes definidas nun arquivo fora do sitio web
 
     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,DB);  // Constantes definidas nun arquivo fora do sitio web
Línea 246: Línea 330:
  
 
</syntaxhighlight>  
 
</syntaxhighlight>  
 +
 +
 +
'''VERSION PDO:'''
 +
:::<syntaxhighlight lang="java" enclose="div" highlight="8,23,28,34" >
 +
<?php
 +
    // Crear conexión PDO
 +
    $conex = new PDO("mysql:host=" . "SERVIDOR" . ";dbname=" . "DB" . ";charset=utf8mb4","USUARIO","PASSWORD");
 +
    $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 +
 +
 +
    // Comezamos a transacción
 +
    $conex->beginTransaction();
 +
 +
    // Primeira inserción
 +
    $stmt = $conex->query("INSERT INTO TABLE_1 (col2) VALUES ('valor')");
 +
    if ($stmt) {
 +
        printf("Novo rexistro có id %d.<br>", $conex->lastInsertId());
 +
    }
 +
 +
    // Segunda inserción
 +
    $stmt = $conex->query("INSERT INTO TABLE_2 (col2) VALUES ('valor')");
 +
    if ($stmt) {
 +
        printf("Novo rexistro có id %d.<br>", $conex->lastInsertId());
 +
    }
 +
 +
    // Se todo vai ben, confirmamos a transacción
 +
    $conex->commit();
 +
}
 +
catch (PDOException $e) {
 +
    // Reverter se hai unha transacción activa
 +
    if (!empty($conex) && $conex->inTransaction()) {
 +
        $conex->rollBack();
 +
    }
 +
    die("Erro PDO: " . htmlentities($e->getMessage()));
 +
}
 +
catch (Exception $e) {
 +
    if (!empty($conex) && $conex->inTransaction()) {
 +
        $conex->rollBack();
 +
    }
 +
    die("Erro xeral: " . htmlentities($e->getMessage()));
 +
}
 +
finally {
 +
    $conex = null; // Pechar conexión (liberar recursos)
 +
}
 +
?>
 +
 +
</syntaxhighlight>
 +
  
  
Línea 260: Línea 392:
 
: Lembrar que se o facemos dende o propio procedemento <u>NON TEMOS QUE FACELO</u> dende PHP.
 
: Lembrar que se o facemos dende o propio procedemento <u>NON TEMOS QUE FACELO</u> dende PHP.
  
−
::<syntaxhighlight lang="java" line enclose="div" highlight="5-9,11,20" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="5-9,11,20" >
 
DELIMITER $$
 
DELIMITER $$
  
Línea 304: Línea 436:
 
: Isto leva consigo que o procedemento vai ter que dar de alta datos nas táboas: libros, autores e libro_autor.
 
: Isto leva consigo que o procedemento vai ter que dar de alta datos nas táboas: libros, autores e libro_autor.
  
−
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
DELIMITER $$
 
DELIMITER $$
  
Línea 370: Línea 502:
  
 
<br />
 
<br />
 +
===Solución Orientada a obxectos===
 
* '''POSIBLE SOLUCIÓN''' (algunha das constantes xa foron creados en solucións anteriores)
 
* '''POSIBLE SOLUCIÓN''' (algunha das constantes xa foron creados en solucións anteriores)
  
Línea 383: Línea 516:
 
define('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
 
define('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
 
define('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
 
define('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
−
define('CONSULTAR_AUTORES_POR_LIBRO','SELECT id,apelidos,nome FROM autores INNER JOIN libro_autor WHERE libro_id=%d order by apelidos,nome');
+
define('CONSULTAR_AUTORES_POR_LIBRO','SELECT id,apelidos,nome FROM autores INNER JOIN libro_autor ON libro_autor.autor_id=autores.id WHERE libro_id=%d order by apelidos,nome');
−
define('INSERIR_LIBRO_AUTOR' 'INSERT INTO libro_autor (libro_id, autor_id) VALUES (%d,%d)');
+
define('INSERIR_LIBRO_AUTOR', 'INSERT INTO libro_autor (libro_id, autor_id) VALUES (%d,%d)');
 
define('BORRAR_LIBRO_AUTOR_POR_LIBRO','DELETE FROM libro_autor WHERE libro_id=%d');
 
define('BORRAR_LIBRO_AUTOR_POR_LIBRO','DELETE FROM libro_autor WHERE libro_id=%d');
  
Línea 442: Línea 575:
 
             if(!empty($_POST['btnLibro']))  {          // Vimos de seleccionar un libro
 
             if(!empty($_POST['btnLibro']))  {          // Vimos de seleccionar un libro
 
                 $listaAutoresLibro = [];
 
                 $listaAutoresLibro = [];
−
                 $consulta = sprintf(CONSULTAR_AUTORES_POR_LIBRO,htmlentities($_POST['lstLibros']));
+
                 $consulta = sprintf(CONSULTAR_AUTORES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
 
                 $resultado = $conex->query($consulta);
 
                 $resultado = $conex->query($consulta);
 
                 while($fila = $resultado->fetch_assoc()) {
 
                 while($fila = $resultado->fetch_assoc()) {
Línea 486: Línea 619:
 
         <div>
 
         <div>
 
             <?php
 
             <?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']));
 
                 if(isset($_GET['error'])) printf("<h2>Houbo un erro: %s</h2>",htmlentities($_GET['error']));
 
             ?>
 
             ?>
Línea 542: Línea 674:
 
catch (mysqli_sql_exception $e) {
 
catch (mysqli_sql_exception $e) {
 
     if(!empty($conex)) $conex->rollback();
 
     if(!empty($conex)) $conex->rollback();
−
     $error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage()));
+
     $error=sprintf("error='Erro mysql: %s'",urlencode($e->getMessage()));
 
}
 
}
 
catch (Expcetion $e) {
 
catch (Expcetion $e) {
 
     if(!empty($conex)) $conex->rollback();
 
     if(!empty($conex)) $conex->rollback();
−
     $error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage()));
+
     $error=sprintf("error='Erro xeral: %s'",urlencode($e->getMessage()));
 
}
 
}
 
finally{
 
finally{
Línea 562: Línea 694:
 
</syntaxhighlight>  
 
</syntaxhighlight>  
  
 +
 +
 +
<br />
 +
===Solución PDO===
 +
* '''POSIBLE SOLUCIÓN''' (algunha das constantes xa foron creados en solucións anteriores)
 +
 +
 +
'''Arquivo: bd.php'''
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
define ('SERVIDOR','localhost');
 +
define ('USUARIO','user_php');
 +
define ('PASSWORD','user_php');
 +
define ('BD','php');
 +
 +
 +
define('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
 +
define('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
 +
define('CONSULTAR_AUTORES_POR_LIBRO','SELECT id,apelidos,nome FROM autores INNER JOIN libro_autor ON libro_autor.autor_id=autores.id
 +
                                      WHERE libro_id=:libro_id order by apelidos,nome');
 +
define('INSERIR_LIBRO_AUTOR', 'INSERT INTO libro_autor (libro_id, autor_id) VALUES (:libro_id,:autor_id)');
 +
define('BORRAR_LIBRO_AUTOR_POR_LIBRO','DELETE FROM libro_autor WHERE libro_id=:libro_id');
 +
 +
</syntaxhighlight>
 +
 +
 +
 +
'''Arquivo: alta_libro_autores.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);
 +
 +
    $result = $conex->query(CONSULTAR_LIBROS);
 +
    $listaLibros = [0 => ['id' => -1, 'titulo' => 'Escolle un libro']];
 +
 +
    // Comprobar se hai filas
 +
    if ($result && $result->rowCount() > 0) {
 +
        while ($fila = $result->fetch(PDO::FETCH_ASSOC)) {
 +
            $listaLibros[] = $fila;
 +
        }
 +
        $result = null;
 +
    }
 +
 +
?>
 +
 +
    <!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 libros-autores</title>
 +
    </head>
 +
 +
    <body>
 +
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method="post">
 +
            <select name='lstLibros'>
 +
                <?php
 +
                $libroSeleccionado = -1;
 +
                if (!empty($_POST['btnLibro'])) $libroSeleccionado = $_POST['lstLibros'];    // Vimos de seleccionar un libro
 +
 +
                foreach ($listaLibros as $libro) {
 +
                    $cadena = '';  // Para facer que o libro se manteña seleccionado ao premer no botón
 +
                    if ($libroSeleccionado == $libro['id']) $cadena = "selected='selected'";
 +
 +
                    printf("<option value=%d %s>%s</option>", htmlentities($libro['id']), $cadena, htmlentities($libro['titulo']));
 +
                }
 +
                ?>
 +
            </select>
 +
            <input type='submit' value='BUSCA AUTORES' name='btnLibro' />
 +
        </form>
 +
 +
        <?php
 +
       
 +
        if (!empty($_POST['btnLibro'])) {          // Vimos de seleccionar un libro
 +
 +
            $result = $conex->query(CONSULTAR_AUTORES);
 +
            $listaAutores=[];
 +
            // Comprobar se hai filas
 +
            if ($result && $result->rowCount() > 0) {
 +
                while ($fila = $result->fetch(PDO::FETCH_ASSOC)) {
 +
                    $listaAutores[] = $fila;
 +
                }
 +
                $result = null;
 +
            }
 +
 +
           
 +
            $listaAutoresLibro = [];
 +
            $stmt = $conex->prepare(CONSULTAR_AUTORES_POR_LIBRO);
 +
            $stmt->bindValue(":libro_id", $_POST['lstLibros'], PDO::PARAM_INT);
 +
            $result = $stmt->execute();
 +
 +
            // Comprobar se hai filas
 +
            if ($result) {
 +
                while ($fila = $stmt->fetch(PDO::FETCH_ASSOC)) {
 +
                    $listaAutoresLibro[] = $fila['id'];
 +
                }
 +
                $result = null;
 +
            }
 +
        ?>
 +
 +
            <form action="procesar_alta_libro_autores.php" method="post">
 +
                <table border="1" style="margin-top:20px">
 +
                    <tr>
 +
                        <th>Autores</th>
 +
                        <th></th>
 +
                    </tr>
 +
                    <?php
 +
                    printf("<input type='hidden' name='libro_id' value=%d", htmlentities($_POST['lstLibros']));  // Necesito enviar o id do libro xunto cos id's dos autores
 +
                    foreach ($listaAutores as $autor) {
 +
                        $cadena = '';
 +
                        if (in_array($autor['id'], $listaAutoresLibro)) { // O autor xa está asociado ao libro
 +
                            $cadena = "checked='checked'";
 +
                        }
 +
                        printf("<tr><td>%s</td>", htmlentities($autor['apelidos'] . ',' . $autor['nome']));
 +
                        printf("<td><input name=autores[] type='checkbox' value=%d %s /></td></tr>", htmlentities($autor['id']), $cadena);
 +
                    }
 +
 +
                    ?>
 +
                </table>
 +
                <div style='margin-top:30px'><input type='submit' value='ASOCIAR' name='btnAsociar' /></div>
 +
            </form>
 +
        <?php
 +
        }  // Fin do if seleccionar un libro
 +
        ?>
 +
 +
        <div>
 +
            <?php
 +
                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: procesar_alta_libro_autores.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);
 +
 +
    // INICIAMOS TRANSACCION
 +
    $conex->beginTransaction();
 +
 +
    if (!empty($_POST['btnAsociar'])){ 
 +
        $stmt = $conex->prepare(BORRAR_LIBRO_AUTOR_POR_LIBRO);    // Temos que poñer prepare
 +
        $stmt->bindValue(':libro_id', $_POST['libro_id'], PDO::PARAM_INT);
 +
        $result = $stmt->execute();
 +
 +
 +
        foreach($_POST['autores'] as $autor){
 +
            $stmt = $conex->prepare(INSERIR_LIBRO_AUTOR);    // Temos que poñer prepare
 +
            $stmt->bindValue(':libro_id', $_POST['libro_id'], PDO::PARAM_INT);
 +
            $stmt->bindValue(':autor_id', $autor, PDO::PARAM_INT);
 +
            $result = $stmt->execute();
 +
        }
 +
    }
 +
    $conex->commit();
 +
 +
}    // Pecha o try do principio da páxina
 +
catch (PDOException $e) {
 +
    if (!empty($conex) && $conex->inTransaction()) {
 +
        $conex->rollBack();
 +
    }
 +
    $error = sprintf("error=PDO:%s", urlencode($e->getMessage()));
 +
} catch (Exception $e) {
 +
    if (!empty($conex) && $conex->inTransaction()) {
 +
        $conex->rollBack();
 +
    }
 +
    $error = sprintf("error=ERRO XERAL:%s", urlencode($e->getMessage()));
 +
}
 +
finally {
 +
    $conex = null; // Pechar a conexión
 +
}
 +
$url = sprintf("alta_libro_autores.php?%s",$error);
 +
header("Location:" . $url);
 +
</syntaxhighlight>
  
  
Línea 596: Línea 938:
  
 
<br />
 
<br />
 +
 +
===Parte Mysql===
 
* '''POSIBLE SOLUCIÓN''' (algúns dos procedementos xa foron creados en solucións anteriores)
 
* '''POSIBLE SOLUCIÓN''' (algúns dos procedementos xa foron creados en solucións anteriores)
  
Línea 661: Línea 1005:
  
  
 +
<br />
 +
===Orientada a obxectos===
  
 
'''Arquivo bd.php''':
 
'''Arquivo bd.php''':
−
::<syntaxhighlight lang="java" line enclose="div" highlight="" >
+
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 
define('PROCEDEMENTO_INSERIR_LIBRO',"INSERIR_LIBRO('%s')");
 
define('PROCEDEMENTO_INSERIR_LIBRO',"INSERIR_LIBRO('%s')");
 
define('PROCEDEMENTO_INSERIR_EXEMPLAR',"INSERIR_EXEMPLAR(%d,%d,'%s',%f,%d)");
 
define('PROCEDEMENTO_INSERIR_EXEMPLAR',"INSERIR_EXEMPLAR(%d,%d,'%s',%f,%d)");
Línea 893: Línea 1239:
 
         ?>
 
         ?>
 
         <?php
 
         <?php
−
            if (!empty($error)) {   // En caso de atopar erros os amosa
+
          if (!empty($error)) {
−
                printf("<h4>$error</h4>");
+
            printf("<h4>%s</h4>", $error);
−
            }
+
          }
 
         ?>
 
         ?>
  
Línea 905: Línea 1251:
  
  
 +
 +
<br />
 +
===Empregando PDO===
 +
 +
'''Arquivo bd.php''':
 +
::<syntaxhighlight lang="java" enclose="div" highlight="" >
 +
define('PROCEDEMENTO_INSERIR_LIBRO',"INSERIR_LIBRO(:titulo)");
 +
define('PROCEDEMENTO_INSERIR_EXEMPLAR',"INSERIR_EXEMPLAR(:id_libro,:id_exemplar,:imaxe,:prezo,:estado)");
 +
define('PROCEDEMENTO_CONSULTAR_ESTADOS','CONSULTAR_ESTADOS()');
 +
</syntaxhighlight>
 +
 +
 +
 +
 +
<br />
 +
'''Arquivo alta_libro_exemplares.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
 +
 +
// *********************************ALTA ************************************************************************//
 +
if (!empty($_POST['btnAlta'])) { // DAMOS DE ALTA A EXAMPLARES E O LIBRO
 +
    foreach ($_POST['lstEstado'] as $indice => $estado) {    // Usamos o índice para buscar os datos no resto dos campos => Fixarse que é nu foreach
 +
        $prezo = $_POST['txtPrezo'][$indice];
 +
        $imaxe = $_POST['txtImaxe'][$indice];
 +
 +
        // Xa temos os datos de cada un dos exemplares
 +
        // Verifacamos que cumpran os criterios de tipo e formato => PRIMEIRO VALIDAMOS. SE SON CORRECTOS TODOS INICIAMOS A OPERACION DE ALTA
 +
        if (!filter_var($estado, FILTER_VALIDATE_INT) || $estado < 0) {
 +
            $error .= "O estado é incorrecto...";
 +
            break;
 +
        }
 +
        if (filter_var(str_replace(',', '.', $prezo), FILTER_VALIDATE_FLOAT) == false || $prezo <= 0 || !preg_match("/[0-9]{1,6}(,[0-9]{1,2})?/", $prezo)) {
 +
            $error .= "O prezo é incorrecto...";
 +
            break;
 +
        }
 +
        $imaxe = filter_var($imaxe, FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
 +
        if (((strlen($imaxe)) > 100) || (strlen($imaxe) == 0)) {    // O número de caracteres está establecido na táboa da base de datos
 +
            $error .= "O nome da imaxe é demasiado grande ou non foi posto...";
 +
            break;
 +
        }
 +
        // Gardamos os datos validados para dalos de alta todos xuntos
 +
        $dato['prezo'] = $prezo;
 +
        $dato['imaxe'] = $imaxe;
 +
        $dato['estado'] = $estado;
 +
        $datos[] = $dato;
 +
    }  // Fin do foreach
 +
 +
    if (empty($error)) {  // Non houbo error, empezamos a operación de alta con transaccións
 +
        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);
 +
 +
            $conex->beginTransaction(); // INICIAMOS TRANSACCION
 +
 +
            $stmt = $conex->prepare("CALL " . PROCEDEMENTO_INSERIR_LIBRO);    // Temos que poñer prepare
 +
            $stmt->bindValue(':titulo', $_POST['txtTitulo'], PDO::PARAM_STR);
 +
            $result = $stmt->execute();
 +
 +
            // Damos de alta ao libro e obtemos o id xerado
 +
            if ($result) {
 +
                $fila = $stmt->fetch(PDO::FETCH_ASSOC);
 +
                switch ($fila['valor_devolto']) {
 +
                    case -1:
 +
                        throw new Exception("Ese libro xa est&aacute; dado de alta na base de datos...<br />");
 +
                    default:
 +
                        $id_libro = $fila['valor_devolto'];    // XA TEMOS O ID AUTONUMERICO DO LIBRO
 +
                }
 +
                $stmt = null;
 +
            } else {  // Sempre ten que devolver algo como temos implementado o proc.
 +
                throw new Exception('Houbo un erro na chamada á base de datos<br />');
 +
            }
 +
 +
            // Temos que dar de alta aos exemplares
 +
            $cont_num_exemplar = 1;  // Número de exemplar
 +
            foreach ($datos as $dato) {
 +
 +
                $stmt = $conex->prepare("CALL " . PROCEDEMENTO_INSERIR_EXEMPLAR);    // Temos que poñer prepare
 +
                $stmt->bindValue(':id_libro', $id_libro, PDO::PARAM_INT);
 +
                $stmt->bindValue(':id_exemplar', $cont_num_exemplar, PDO::PARAM_INT);
 +
                $stmt->bindValue(':imaxe', $dato['imaxe'], PDO::PARAM_STR);
 +
                $stmt->bindValue(':prezo', str_replace(',', '.', $dato['prezo']), PDO::PARAM_STR); // NON EXISTE DOUBLE EN PARAM_ por iso temos que reemplazar a coma polo punto
 +
                $stmt->bindValue(':estado', $dato['estado'], PDO::PARAM_INT);
 +
 +
                $result = $stmt->execute();
 +
                if (!$result) {  // FIXARSE QUE NON LANZAMOS EXCEPCION => DEIXA DADOS DE ALTA ATA O MOMENTO DO ERRO
 +
                    $error .= 'Houbo un erro na alta dos exemplares';
 +
                    break;
 +
                }
 +
 +
                $cont_num_exemplar += 1;
 +
            }  // Fin do For por cada exemplar
 +
 +
            $conex->commit();
 +
        }  // 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) {
 +
            if (!empty($conex) && $conex->inTransaction()) {
 +
                $conex->rollBack();
 +
            }
 +
            $error = sprintf("erro PDO:%s", $e->getMessage());
 +
        } catch (Exception $e) {
 +
            if (!empty($conex) && $conex->inTransaction()) {
 +
                $conex->rollBack();
 +
            }
 +
            $error = sprintf("erro XERAL:%s", $e->getMessage());
 +
        } finally {
 +
            $conex = null; // Pechar a conexión
 +
        }
 +
    }  // Fin de (empty($error)
 +
}
 +
 +
// *********************************FIN DE ALTA ************************************************************************//
 +
?>
 +
 +
<!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>Transaccións</title>
 +
</head>
 +
 +
<body>
 +
    <?php
 +
    $titulo = '';
 +
    $num_exemplares = '';
 +
    if (!empty($_POST['btnEnviar'])) {  // Prememos o botón que indica o título e o número de exemplares
 +
        $titulo = filter_var($_POST['txtTitulo'], FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
 +
        if (strlen($titulo) > 200) {  // Non pode ter unha lonxitude maior que o campo da base de datos
 +
            $error = 'A lonxitude do título non pode ser maior que 200 caracteres...<br />';
 +
        }
 +
        $num_exemplares = $_POST['txtNumExemplares'];
 +
        if (!filter_var($num_exemplares, FILTER_VALIDATE_INT) || $num_exemplares > 9 || $num_exemplares < 1) {
 +
            $error .= "O número de exemplares ten que ser numérico e non pode ser maior que 9<br />";
 +
        }
 +
        if (strlen($titulo) == 0) {
 +
            $error .= 'O título é obrigatorio...<br />';
 +
        }
 +
    }
 +
    ?>
 +
 +
    <?php
 +
    $inhabilitar = '';
 +
    if (!empty($_POST['btnEnviar']) && (empty($error))) {  // Facemos que a caixa de texto e todo e demais quede inhabilitado
 +
        $inhabilitar = "disabled='disabled'";                  // Unha vez enviamos os datos do titulo e num exemplares
 +
    }
 +
    ?>
 +
    <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
 +
        <div>Titulo Libro: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'", $titulo); ?> type='text' size='100' maxlength="300" name='txtTitulo' /></div>
 +
        <div>Num. Exemplares: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'", $num_exemplares); ?> type='number' min='1' max='9' size="2" maxlength="1" name='txtNumExemplares' /> (&lt;10) </div>
 +
        <input <?php echo $inhabilitar ?> type="submit" value='Enviar' name='btnEnviar' />
 +
    </form>
 +
 +
 +
    <?php
 +
    if ($titulo && $num_exemplares) {    // Enviamos datos
 +
        // NON IMOS A FACER UNHA CONSULTA A TABOA ESTADOS POR CADA UN DOS EXEMPLARES. NON TEN SENTIDO.
 +
        // PODERIAMOS PERCORRER O ARRAY DE RESULTADOS E AO REMATAR VOLVER AO PRINCIPIO: http://php.net/manual/es/mysqli-result.data-seek.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);
 +
 +
            $proc = sprintf("CALL %s", PROCEDEMENTO_CONSULTAR_ESTADOS);
 +
            $result = $conex->query($proc);
 +
            if ($result && $result->rowCount() > 0) {
 +
                while ($fila = $result->fetch(PDO::FETCH_ASSOC)) {
 +
                    $estados[] = $fila;
 +
                }
 +
                $result = null;
 +
            }
 +
        } catch (PDOException $e) {
 +
            $error .= sprintf("Erro mysql: %s", htmlentities($e->getMessage()));
 +
        } catch (Exception $e) {
 +
            $error .= sprintf("Erro xeral: %s", htmlentities($e->getMessage()));
 +
        } finally {
 +
            $conex = null; // Pechar a conexión
 +
        } // 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
 +
 +
        printf("<form action=\"%s\" method='post'>", htmlentities($_SERVER['PHP_SELF']));
 +
        printf("<input type='hidden' value='%s' name='txtTitulo' />", htmlentities($_POST['txtTitulo'])); // Como temos dous formularios, temos que gardar o texto do título nun campo oculto
 +
        printf("<table>");
 +
        for ($cont = 0; $cont < $num_exemplares; $cont++) {
 +
            echo "<th>Estado</th><th>Imaxe</th><th>Prezo</th>";
 +
            echo "<tr>";
 +
            echo "<td>";    // ESTADO
 +
            printf("<select name='lstEstado[]>");  // Poñemos o nome en forma de array xa que imos recibir varios exemplares con datos.
 +
            foreach ($estados as $estado) {
 +
                printf("<option value='%d'>%s</option>", htmlentities($estado['id']), htmlentities($estado['descripcion']));
 +
            }
 +
            printf("</select>");
 +
            echo "</td>";
 +
            echo "<td>";    // IMAXE
 +
            printf("<input type='text' size='50' maxlength='70' name='txtImaxe[]' />");
 +
            echo "</td>";
 +
            echo "<td>";    // PREZO
 +
            printf("<input type='text' size='9' maxlength='9' name='txtPrezo[]' /> Euros (XXXXXX,YY)");
 +
            echo "</td>";
 +
            echo "</tr>";
 +
        }
 +
 +
        printf("</table>");
 +
        printf("<input type='submit' value='ALTA' name='btnAlta' />");
 +
        printf("<input type='reset' value='CANCELAR' name='btnCancelar' />");
 +
        printf("</form>");
 +
    }
 +
    ?>
 +
    <?php
 +
    if (!empty($error)) {
 +
        printf("<h4>%s</h4>", $error);
 +
    }
 +
    ?>
 +
 +
</body>
 +
 +
</html>
 +
</syntaxhighlight>
  
  
Línea 929: Línea 1503:
  
  
−
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2023).
+
<br> -- [[Usuario:angelfg|Ángel D. Fernández González]] -- (2025).

Revisión del 19:16 6 nov 2025

Introducción

  • En certas ocasións imos necesitar facer varias operacións que conleven modificacións nas táboas da base de datos (operacións de INSERT, UPDATE, DELETE). Se queremos que nunha páxina php se fagan varias modificacións e queremos que se hai calquera problema nunha delas, deixe o estado da base de datos xusto a como estaba antes de facer a primeira operación, necesitamos facer uso das transaccións.



  • Por exemplo, no exemplo que estamos a desenvolver:
  • Podemos crear unha páxina PHP cun formulario para dar de alta un libro xunto cos exemplares.
  • Podemos crear unha páxina PHP que relacione libros xunto cos autores que o escribiron.
  • Podemos dar de baixa un libro. Isto leva consigo dar de baixa o libro máis os exemplares máis os datos da táboas LIBROS_AUTORES.
  • Outro caso típico é o carrito da compra. Cando o usuario remata o proceso de compra e procede a realizar o pedido, necesitaremos gardar os produtos que conforman o pedido na nosa base de datos.
Queremos que se garden todos os produtos.


Normalmente imos queres que ditas operacións se fagan todas e en caso de erro, necesitaremos volver ao estado anterior ao comezo do inicio das operacións.
  • Isto o podemos facer grazas as TRANSACCIÓNS.
Unha transacción nos garante que o conxunto de operacións que están dentro dunha transacción se farán todas ou non se fará ningunha.
  • Normalmente cada operación individual é 'considerada' coma unha transacción en si mesma, de tal forma o ou ten éxito ou os cambios non se gardan na base de datos.
  • Máis información sobre Transaccións:



  • Cando falamos de transaccións outro factor a ter en conta é o chamado Nivel de illamento.
Imaxinemos que estamos dentro dunha transacción e estou lendo o número de filas dunha táboa. Se outro usuario engade unha nova fila despois de que eu lera o número, e eu dentro da transacción volvo a ler o número de filas. Terei dúas cantidades diferentes dentro da mesma transacción.
Para evitalo podemos facer que a táboa que estou a utilizar dentro da transacción quede 'bloqueada' e non se poida modificar ata que eu remate.
Este e outros problemas que poden darse no uso de transaccións se intentan evitar cun nivel de illamento determinado.
Cada nivel de illamento soluciona algún dos problemas que se poden dar.
  • Máis información sobre Niveis de illamento en Mysql:



  • Dos niveis de illamento (que se basean en bloquear rexistros de táboas) se pode provocar outro problema, que é o Interbloqueo ou Deadlock.
Isto prodúcese cando unha transacción_1 está esperando o acceso a un recurso que está sendo bloqueado por outra transacción_2 e esta outra transacción_2 está esperando por un recurso que está sendo bloqueado pola transacción_1.




  • Nota importante: Lembrar que xa expliquei ao abrir a conexión, no control de erros así como tamén nas operacións de consulta/alta/baixa/modificar que se estamos a empregar á versión PHP 8.1 ou superior, vanse lanzar excepcións que teremos que teremos que capturar con catch.



Funcionamento

  • En PHP, para poder utilizar transaccións necesitamos:


  • Unha transacción a podemos facer dende a páxina PHP ou dende o xestor Mysql dentro dun procedemento almacenado.
  • Para realizar unha transacción o proceso sempre é o mesmo en todas as linguaxes e xestores.
  1. Iniciar a transacción. Normalmente farase coa orde 'START TRANSACTION' (outros xestores teñen coma orde BEGIN TRANSACTION)
  2. Realizar as operacións que conforman as transaccións.
  3. En caso de erro en algunha das operacións anteriores executar a orde 'ROLLBACK' e saír
  4. En caso de que todo se execute correctamente, entón confirmamos a transacción e os cambios se gardan definitivamente na base de datos, coa orde: 'COMMIT'



  • Para iniciar unha transacción temos dúas opcións:
  • Versión procedimental:
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
mysqli_autocommit($conex,false);
  • Versión obxectual:
$conex= new mysqli(HOST, USER, PASSWORD, BD);
$conex->autocommit(false);


Desta forma as operacións que vaian a continuación desta orde non se farán efectivas ata atopar un commit.



NECESITAMOS QUE A VERSIÓN PHP SEXA 5.5 OU SUPERIOR.
  • Versión procedimental:
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
mysqli_begin_transaction($conex,MYSQLI_TRANS_START_READ_ONLY);
  • Versión obxectual:
$conex= new mysqli(HOST, USER, PASSWORD, BD);
$conex->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);


  • Versión PDO:
$conex = new PDO("mysql:host=" . HOST . ";dbname=" . BD . ";charset=utf8mb4", USER, PASSWORD);

// Desactivar o modo autocommit
$conex->beginTransaction();



Esta segunda forma leva como parámetro unha bandeira que vai indicar que dentro da transacción non vai poderse facer operacións que modifiquen os contidos das táboas da base de datos.
Se a transacción necesita facer operacións contra a base de datos, levaría o flag MYSQLI_TRANS_START_READ_WRITE.
Máis información neste enlace.


  • Unha vez iniciada a transacción faremos as operacións necesarias sobre a base de datos. En caso de erro nalgunha delas, teremos que facer un rollback coa función rollback.
  • Versión procedimental:
$conex = mysqli_connect(HOST, USER, PASSWORD, BD);
mysqli_begin_transaction($conex,MYSQLI_TRANS_START_READ_ONLY);

.........
OPERACIÓN
Si erro
   mysqli_rollback($conex);
   die('Houbo un erro...');
  • Versión obxectual:
$conex= new mysqli(HOST, USER, PASSWORD, BD);
$conex->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);

.........
OPERACIÓN
Si erro
   $conex->rollback();
   die('Houbo un erro...');


  • PDO:
$conex = new PDO("mysql:host=" . HOST . ";dbname=" . BD . ";charset=utf8mb4", USER, PASSWORD);
$conex->beginTransaction();

.........
OPERACIÓN
Si erro
    if ($conex->inTransaction()) {
        $conex->rollBack();
    }
    die('Houbo un erro...');



  • Exemplo obxectual empregando unha versión de PHP 8.1 onde as ordes contra a base de datos poden provocar unha excepción:
try {
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,DB);   // Constantes definidas nun arquivo fora do sitio web
    $conex->set_charset('utf8mb4');
    
    $conex->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
    $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);
    }
    $result=$conex->query("INSERT INTO TABLE_2 (col2) VALUES ('valor')");
    if ($result){
       // Operación correcta
       printf ("Novo rexistro có id %d.\n", $conex->insert_id);
    }

}   // Pecha o try da parte superior da páxina
catch (mysqli_sql_exception $e) {
    if(!empty($conex)) $conex->rollback();
    die("Erro mysql: " . $e->getMessage());
}
catch (Expcetion $e) {
    if(!empty($conex)) $conex->rollback();
    die("Erro xeral: " . $e->getMessage());
}
finally{
  if(!empty($conex)){
      $conex->close();
  }
}


  • Exemplo PDO empregando unha versión de PHP 8.1 onde as ordes contra a base de datos poden provocar unha excepción:
<?php
try {
    // Crear conexión PDO
    $conex = new PDO("mysql:host=" . "SERVIDOR" . ";dbname=" . "DB" . ";charset=utf8mb4","USUARIO","PASSWORD");
    $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);


    // Comezamos a transacción
    $conex->beginTransaction();

    // Primeira inserción
    $stmt = $conex->query("INSERT INTO TABLE_1 (col2) VALUES ('valor')");
    if ($stmt) {
        printf("Novo rexistro có id %d.<br>", $conex->lastInsertId());
    }

    // Segunda inserción
    $stmt = $conex->query("INSERT INTO TABLE_2 (col2) VALUES ('valor')");
    if ($stmt) {
        printf("Novo rexistro có id %d.<br>", $conex->lastInsertId());
    }

}
catch (PDOException $e) {
    // Reverter se hai unha transacción activa
    if (!empty($conex) && $conex->inTransaction()) {
        $conex->rollBack();
    }
    die("Erro PDO: " . htmlentities($e->getMessage()));
}
catch (Exception $e) {
    if (!empty($conex) && $conex->inTransaction()) {
        $conex->rollBack();
    }
    die("Erro xeral: " . htmlentities($e->getMessage()));
}
finally {
    $conex = null; // Pechar conexión (liberar recursos)
}
?>


  • Versión procedimental:
mysqli_commit($conex);
  • Versión obxectual:
$conex->commit();
  • Versión PDO:
$conex->commit();


  • Se establecimos autocommit(false) deberemos volvelo a establecer en True.
$conex->autocommit(true);


  • Ao rematar todas as operacións sobre a base de datos pecharemos a conexión na versión procedimental ou poñemos a null na versión PDO.
$conex->close();



O exemplo anterior de forma completa (empregando unha versión de PHP 8.1 onde as ordes contra a base de datos poden provocar unha excepción:): VERSION OBXECTUAL

try {
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,DB);   // Constantes definidas nun arquivo fora do sitio web
    $conex->set_charset('utf8mb4');
    
    $conex->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
    $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);
    }
    $result=$conex->query("INSERT INTO TABLE_2 (col2) VALUES ('valor')");
    if ($result){
       // Operación correcta
       printf ("Novo rexistro có id %d.\n", $conex->insert_id);
    }
    $conex->commit();

}   // Pecha o try da parte superior da páxina
catch (mysqli_sql_exception $e) {
    if(!empty($conex)) $conex->rollback();
    printf("Erro mysql: " . $e->getMessage());  // Se non poñemos die executa o finally. Se non habería que poñer o autocommit aquí tamén

}
catch (Expcetion $e) {
    if(!empty($conex)) $conex->rollback();
    printf("Erro xeral: " . $e->getMessage());  // Se non poñemos die executa o finally. Se non habería que poñer o autocommit aquí tamén
}
finally{
  if(!empty($conex)){
      $conex->close();
  }
  $conex->autocommit(true);

}


VERSION PDO:

<?php
    // Crear conexión PDO
    $conex = new PDO("mysql:host=" . "SERVIDOR" . ";dbname=" . "DB" . ";charset=utf8mb4","USUARIO","PASSWORD");
    $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);


    // Comezamos a transacción
    $conex->beginTransaction();

    // Primeira inserción
    $stmt = $conex->query("INSERT INTO TABLE_1 (col2) VALUES ('valor')");
    if ($stmt) {
        printf("Novo rexistro có id %d.<br>", $conex->lastInsertId());
    }

    // Segunda inserción
    $stmt = $conex->query("INSERT INTO TABLE_2 (col2) VALUES ('valor')");
    if ($stmt) {
        printf("Novo rexistro có id %d.<br>", $conex->lastInsertId());
    }

    // Se todo vai ben, confirmamos a transacción
    $conex->commit();
}
catch (PDOException $e) {
    // Reverter se hai unha transacción activa
    if (!empty($conex) && $conex->inTransaction()) {
        $conex->rollBack();
    }
    die("Erro PDO: " . htmlentities($e->getMessage()));
}
catch (Exception $e) {
    if (!empty($conex) && $conex->inTransaction()) {
        $conex->rollBack();
    }
    die("Erro xeral: " . htmlentities($e->getMessage()));
}
finally {
    $conex = null; // Pechar conexión (liberar recursos)
}
?>





Transaccións en MYSQL dentro de procedementos almacenados

  • Outra forma de realizar as transaccións é que poidamos enviar os datos a un procedemento almacenado de Mysql e que dende o procedemento se faga a transacción.
  • O proceso é moi parecido ao feito dende PHP.
Lembrar que se o facemos dende o propio procedemento NON TEMOS QUE FACELO dende PHP.
DELIMITER $$

CREATE PROCEDURE `PROCEDEMENTO_CON_TRANSACCION`(_param1 int,_param2 varchar(50),_param3 varchar(200))
BEGIN
	DECLARE EXIT HANDLER FOR SQLEXCEPTION
	BEGIN
		ROLLBACK;
		SELECT -1 as error;
	END;

	START TRANSACTION;

	INSERT INTO TABLE_1(col1,col2)
	VALUES (_param2,_param3);  -- Supoñemos que esta táboa devolve un valor clave autonumérico.

	INSERT INTO TABLE_2(col1,col2)
	VALUES (_param1,last_insert_id());

	
	COMMIT;
	SELECT 0 AS error;

END
$$
  • Liñas 5-9: Capturamos calquera erro que se produza e nese caso enviamos a PHP un código -1 no select facendo un rollback da transacción.
  • Liña 11: Iniciamos a transacción.
  • Liñas 20 e 21: Confirmamos a transacción se todo foi ben e enviamos un 0 como código ao PHP.


  • Comentar que se facemos as transaccións dende procedementos almacenados teremos que capturar as posibles excepcións que se produzan para facer o rollback.
Máis información: Captura de erros en Mysql.




  • Por exemplo, no caso da base de datos que estamos a desenvolver nesta sección, poderíamos crear un procedemento almacenado que dea de alta a un único autor xunto có libro.
Isto leva consigo que o procedemento vai ter que dar de alta datos nas táboas: libros, autores e libro_autor.
DELIMITER $$

CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_AUTORES_LIBRO`(p_titulo varchar(300),p_nome varchar(50),p_apelidos varchar(200))
BEGIN
	DECLARE v_id_libro int;

	DECLARE EXIT HANDLER FOR SQLEXCEPTION
	BEGIN
		ROLLBACK;
		SELECT -1 as error; 
	END;

	START TRANSACTION;
	INSERT INTO libros(titulo)
    VALUES (p_titulo);
    
    SET v_id_libro = last_insert_id();
      

	INSERT INTO autores(nome,apelidos)
	VALUES (p_nome,p_apelido);

	INSERT INTO LIBROS_AUTORES (libro_id,autor_id)
	VALUES (v_id_libro,last_insert_id());
	
	COMMIT;
	SELECT 0 AS error;

END$$
DELIMITER ;




Exercicio proposto 1

  • Asociar libros a autores.
  • A idea é escoller dunha lista (<select>) un libro.
Unha vez seleccionado un libro e premido o botón BUSCAR aparecerán nunha lista de checkboxes todos os autores. O libro debe manterse seleccionado ao amosar todos os autores.
  • O usuario poderá checkear varios autores e serán asociados a dito libro.
Os autores xa asociados ao libro terán que estar chequeados.
  • Ao premer o botón de ALTA, se asociarán ao libro seleccionado.
Isto levará consigo as operacións de baixa de todos os autores do libro e a de alta sobre a táboa LIBROS_AUTORES por cada un dos autores seleccionados.


Pista: Lembrar que en html, se damos de nome a un input có seguinte formato: dato[] e repetimos este nome en varios input, cando enviemos o dato, en PHP o poderemos recoller en formato de array.
  • Para separar a lóxico, crea as seguintes páxinas:
  • alta_libro_autores.php => Amosa o libro e os autores
  • procesar_alta_libro_autores.php => Procesa a alta e volve a amosar alta_libroautores.php




Solución Orientada a obxectos

  • POSIBLE SOLUCIÓN (algunha das constantes xa foron creados en solucións anteriores)


Arquivo: bd.php

define ('SERVIDOR','localhost');
define ('USUARIO','user_php');
define ('PASSWORD','user_php');
define ('BD','php');


define('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
define('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
define('CONSULTAR_AUTORES_POR_LIBRO','SELECT id,apelidos,nome FROM autores INNER JOIN libro_autor ON libro_autor.autor_id=autores.id WHERE libro_id=%d order by apelidos,nome');
define('INSERIR_LIBRO_AUTOR', 'INSERT INTO libro_autor (libro_id, autor_id) VALUES (%d,%d)');
define('BORRAR_LIBRO_AUTOR_POR_LIBRO','DELETE FROM libro_autor WHERE libro_id=%d');


Arquivo: alta_libro_autores.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');
    $resultado = $conex->query(CONSULTAR_LIBROS);

    $listaLibros = [0 => ['id' => -1, 'titulo' => 'Escolle un libro']];
    while($fila = $resultado->fetch_assoc()) {
        $listaLibros[] = $fila;
    }
    $resultado->free();

?>
<!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 libros-autores</title>
    </head>
    <body>

        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method="post">
            <select name='lstLibros'>
                <?php 
                    $libroSeleccionado=-1;
                    if(!empty($_POST['btnLibro'])) $libroSeleccionado = $_POST['lstLibros'];    // Vimos de seleccionar un libro

                    foreach ($listaLibros as $libro) {
                        $cadena = '';   // Para facer que o libro se manteña seleccionado ao premer no botón
                        if($libroSeleccionado == $libro['id']) $cadena = "selected='selected'";
                        
                        printf("<option value=%d %s>%s</option>",htmlentities($libro['id']),$cadena,htmlentities($libro['titulo']));
                    }
                ?>
            </select>
            <input type='submit' value='BUSCA AUTORES' name='btnLibro' />
        </form>

        <?php 
            if(!empty($_POST['btnLibro']))  {           // Vimos de seleccionar un libro
                $listaAutoresLibro = [];
                $consulta = sprintf(CONSULTAR_AUTORES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
                $resultado = $conex->query($consulta);
                while($fila = $resultado->fetch_assoc()) {
                    $listaAutoresLibro[] = $fila['id'];
                }
                $resultado->free();
            
                $resultado = $conex->query(CONSULTAR_AUTORES);
                while($fila = $resultado->fetch_assoc()) {
                    $listaAutores[] = $fila;
                }
                $resultado->free();
        ?>

        <form action="procesar_alta_libro_autores.php" method="post">
            <table border="1" style="margin-top:20px">
                <tr>
                    <th>Autores</th>
                    <th></th>
                </tr>
                <?php 
                printf("<input type='hidden' name='libro_id' value=%d",htmlentities($_POST['lstLibros']));  // Necesito enviar o id do libro xunto cos id's dos autores
                foreach($listaAutores as $autor){
                    $cadena = '';
                    if (in_array($autor['id'],$listaAutoresLibro)){ // O autor xa está asociado ao libro
                        $cadena = "checked='checked'";
                    }
                    printf("<tr><td>%s</td>",htmlentities($autor['apelidos'] . ',' . $autor['nome']));
                    printf("<td><input name=autores[] type='checkbox' value=%d %s /></td></tr>",htmlentities($autor['id']), $cadena);
                }

                ?>
            </table>
            <div style='margin-top:30px'><input type='submit' value='ASOCIAR' name='btnAsociar' /></div>
        </form>


        <?php 
            }   // Fin do if seleccionar un libro
        ?>


        <div>
            <?php
                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 (Expcetion $e) {
    die("Erro xeral: " . $e->getMessage());
}
finally{
  if(!empty($conex)){
      $conex->close();
  }
}
?>


Arquivo: procesar_alta_libro_autores.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');
    $conex->autocommit(false);
    // Xestionamos a operacion de asociar
    if (!empty($_POST['btnAsociar'])){  
        $libro_id = $conex->real_escape_string($_POST['libro_id']);
        $query = sprintf(BORRAR_LIBRO_AUTOR_POR_LIBRO,$libro_id);   // Borra todos los autores do libro dado
        $conex->query($query);

        foreach($_POST['autores'] as $autor){
            $query = sprintf(INSERIR_LIBRO_AUTOR,$libro_id,$conex->real_escape_string($autor));
            $conex->query($query);
        }
    }
    $conex->commit();
}   // 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) {
    if(!empty($conex)) $conex->rollback();
    $error=sprintf("error='Erro mysql: %s'",urlencode($e->getMessage()));
}
catch (Expcetion $e) {
    if(!empty($conex)) $conex->rollback();
    $error=sprintf("error='Erro xeral: %s'",urlencode($e->getMessage()));
}
finally{
    if(!empty($conex)){
        $conex->autocommit(true);
        $conex->close();
    }
}

$url = sprintf("alta_libro_autores.php?%s",$error);
header("Location:" . $url);

?>



Solución PDO

  • POSIBLE SOLUCIÓN (algunha das constantes xa foron creados en solucións anteriores)


Arquivo: bd.php

define ('SERVIDOR','localhost');
define ('USUARIO','user_php');
define ('PASSWORD','user_php');
define ('BD','php');


define('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
define('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
define('CONSULTAR_AUTORES_POR_LIBRO','SELECT id,apelidos,nome FROM autores INNER JOIN libro_autor ON libro_autor.autor_id=autores.id
                                      WHERE libro_id=:libro_id order by apelidos,nome');
define('INSERIR_LIBRO_AUTOR', 'INSERT INTO libro_autor (libro_id, autor_id) VALUES (:libro_id,:autor_id)');
define('BORRAR_LIBRO_AUTOR_POR_LIBRO','DELETE FROM libro_autor WHERE libro_id=:libro_id');


Arquivo: alta_libro_autores.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);

    $result = $conex->query(CONSULTAR_LIBROS);
    $listaLibros = [0 => ['id' => -1, 'titulo' => 'Escolle un libro']];

    // Comprobar se hai filas
    if ($result && $result->rowCount() > 0) {
        while ($fila = $result->fetch(PDO::FETCH_ASSOC)) {
            $listaLibros[] = $fila;
        }
        $result = null;
    }

?>

    <!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 libros-autores</title>
    </head>

    <body>
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method="post">
            <select name='lstLibros'>
                <?php
                $libroSeleccionado = -1;
                if (!empty($_POST['btnLibro'])) $libroSeleccionado = $_POST['lstLibros'];    // Vimos de seleccionar un libro

                foreach ($listaLibros as $libro) {
                    $cadena = '';   // Para facer que o libro se manteña seleccionado ao premer no botón
                    if ($libroSeleccionado == $libro['id']) $cadena = "selected='selected'";

                    printf("<option value=%d %s>%s</option>", htmlentities($libro['id']), $cadena, htmlentities($libro['titulo']));
                }
                ?>
            </select>
            <input type='submit' value='BUSCA AUTORES' name='btnLibro' />
        </form>

        <?php
        
        if (!empty($_POST['btnLibro'])) {           // Vimos de seleccionar un libro

            $result = $conex->query(CONSULTAR_AUTORES);
            $listaAutores=[];
            // Comprobar se hai filas
            if ($result && $result->rowCount() > 0) {
                while ($fila = $result->fetch(PDO::FETCH_ASSOC)) {
                    $listaAutores[] = $fila;
                }
                $result = null;
            }

            
            $listaAutoresLibro = [];
            $stmt = $conex->prepare(CONSULTAR_AUTORES_POR_LIBRO);
            $stmt->bindValue(":libro_id", $_POST['lstLibros'], PDO::PARAM_INT);
            $result = $stmt->execute();

            // Comprobar se hai filas
            if ($result) {
                while ($fila = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    $listaAutoresLibro[] = $fila['id'];
                }
                $result = null;
            }
        ?>

            <form action="procesar_alta_libro_autores.php" method="post">
                <table border="1" style="margin-top:20px">
                    <tr>
                        <th>Autores</th>
                        <th></th>
                    </tr>
                    <?php
                    printf("<input type='hidden' name='libro_id' value=%d", htmlentities($_POST['lstLibros']));  // Necesito enviar o id do libro xunto cos id's dos autores
                    foreach ($listaAutores as $autor) {
                        $cadena = '';
                        if (in_array($autor['id'], $listaAutoresLibro)) { // O autor xa está asociado ao libro
                            $cadena = "checked='checked'";
                        }
                        printf("<tr><td>%s</td>", htmlentities($autor['apelidos'] . ',' . $autor['nome']));
                        printf("<td><input name=autores[] type='checkbox' value=%d %s /></td></tr>", htmlentities($autor['id']), $cadena);
                    }

                    ?>
                </table>
                <div style='margin-top:30px'><input type='submit' value='ASOCIAR' name='btnAsociar' /></div>
            </form>
        <?php
        }   // Fin do if seleccionar un libro
        ?>

        <div>
            <?php
                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: procesar_alta_libro_autores.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);

    // INICIAMOS TRANSACCION
    $conex->beginTransaction();

    if (!empty($_POST['btnAsociar'])){  
        $stmt = $conex->prepare(BORRAR_LIBRO_AUTOR_POR_LIBRO);    // Temos que poñer prepare
        $stmt->bindValue(':libro_id', $_POST['libro_id'], PDO::PARAM_INT);
        $result = $stmt->execute();


        foreach($_POST['autores'] as $autor){
            $stmt = $conex->prepare(INSERIR_LIBRO_AUTOR);    // Temos que poñer prepare
            $stmt->bindValue(':libro_id', $_POST['libro_id'], PDO::PARAM_INT);
            $stmt->bindValue(':autor_id', $autor, PDO::PARAM_INT);
            $result = $stmt->execute();
        }
    }
    $conex->commit();

}    // Pecha o try do principio da páxina
catch (PDOException $e) {
    if (!empty($conex) && $conex->inTransaction()) {
        $conex->rollBack();
    }
    $error = sprintf("error=PDO:%s", urlencode($e->getMessage()));
} catch (Exception $e) {
    if (!empty($conex) && $conex->inTransaction()) {
        $conex->rollBack();
    }
    $error = sprintf("error=ERRO XERAL:%s", urlencode($e->getMessage()));
} 
finally {
    $conex = null; // Pechar a conexión
}
$url = sprintf("alta_libro_autores.php?%s",$error);
header("Location:" . $url);




Exercicio proposto 2: Transaccións en MYSQL dentro de procedementos almacenados

  • Imos crear unha páxina de nome alta_libro_exemplares.php.
Dita páxina terá un formulario onde indicaremos o título do libro e o número de exemplares que queremos dar de alta.
Ao premer o botón de 'Alta' aparecerán en forma de lista (o podedes implementar cunha táboa) un formulario (unha por cada exemplar) cos datos: imaxe-prezo-estado.
Os datos anteriores (título e número de exemplares) non se poderán cambiar.
  • Os campos do formulario serán dos seguintes tipo:
  • Estado: Será un combobox (select en html) cos estados da táboa estados.
  • Imaxe: cambiaremos isto máis adiante. Por agora deixade un campo de texto.
  • Prezo: Prezo da imaxe.
  • Como sempre deberedes validar os datos e que teñan o tipo/formato correcto.
  • No segundo formulario aparecerá un botón de 'Alta' e outro de 'Cancelar'.
  • Ao premer o botón de alta daremos de alta o libro, obteremos o id autoxerado e usaremos ese id para dar de alta aos exemplares.
  • Se prememos o botón 'Cancelar' desaparecerá o segundo formulario e limparase o primeiro para que poidamos dar de alta un novo libro.





Parte Mysql

  • POSIBLE SOLUCIÓN (algúns dos procedementos xa foron creados en solucións anteriores)

MYSQL

Procedemento: CONSULTAR_ESTADOS

DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `CONSULTAR_ESTADOS`()
BEGIN
	SELECT id,descripcion
	FROM estados
	ORDER BY descripcion;

END$$
DELIMITER ;



Procedemento: INSERIR_LIBRO

DELIMITER $$

CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_LIBRO`(p_titulo varchar(300))
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 libros
	WHERE upper(titulo)=upper(p_titulo);
 
	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 libros (titulo)
	VALUES (p_titulo);

	SELECT last_insert_id() as valor_devolto;
END$$
DELIMITER ;



Procedemento: INSERIR_EXEMPLAR

DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_EXEMPLAR`(p_libro_id int,p_id_exemplar int,p_imaxe varchar(100),p_prezo decimal(6,2),p_estado_id int)
BEGIN
	INSERT INTO exemplares (libro_id,id_exemplar,imaxe,prezo,estado_id)
	VALUES (p_libro_id,p_id_exemplar,p_imaxe,p_prezo,p_estado_id);

END$$
DELIMITER ;



Orientada a obxectos

Arquivo bd.php:

define('PROCEDEMENTO_INSERIR_LIBRO',"INSERIR_LIBRO('%s')");
define('PROCEDEMENTO_INSERIR_EXEMPLAR',"INSERIR_EXEMPLAR(%d,%d,'%s',%f,%d)");
define('PROCEDEMENTO_CONSULTAR_ESTADOS','CONSULTAR_ESTADOS()');




Arquivo alta_libro_exemplares.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 

// *********************************ALTA ************************************************************************//
if (!empty($_POST['btnAlta'])){ // DAMOS DE ALTA A EXAMPLARES E O LIBRO
    foreach ($_POST['lstEstado'] as $indice => $estado){    // Usamos o índice para buscar os datos no resto dos campos => Fixarse que é nu foreach
        $prezo = $_POST['txtPrezo'][$indice];
        $imaxe = $_POST['txtImaxe'][$indice];

        // Xa temos os datos de cada un dos exemplares
        // Verifacamos que cumpran os criterios de tipo e formato => PRIMEIRO VALIDAMOS. SE SON CORRECTOS TODOS INICIAMOS A OPERACION DE ALTA
        if (!filter_var($estado,FILTER_VALIDATE_INT) || $estado<0){
            $error .= "O estado é incorrecto...";
            break;
        }
        if (filter_var(str_replace(',', '.', $prezo),FILTER_VALIDATE_FLOAT)==false || $prezo<=0 || !preg_match("/[0-9]{1,6}(,[0-9]{1,2})?/",$prezo)){
            $error .= "O prezo é incorrecto...";
            break;
        }
        $imaxe=filter_var($imaxe,FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
        if(((strlen($imaxe))>100) || (strlen($imaxe)==0)) {    // O número de caracteres está establecido na táboa da base de datos
            $error .= "O nome da imaxe é demasiado grande ou non foi posto...";
            break;
        }
        // Gardamos os datos validados para dalos de alta todos xuntos
        $dato['prezo'] = $prezo;
        $dato['imaxe'] = $imaxe;
        $dato['estado'] = $estado;
        $datos[] = $dato;
    }   // Fin do foreach
    
    
    if(empty($error)){  // Non houbo error, empezamos a operación de alta con transaccións
        try{

            $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   
            $conex->set_charset('utf8mb4');
            
            $conex->autocommit(false);  // Iniciamos a transcción
            
            // Damos de alta ao libro e obtemos o id xerado
            $titulo = $conex->real_escape_string($_POST['txtTitulo']);
            $proc=sprintf(PROCEDEMENTO_INSERIR_LIBRO,$titulo);
            $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:
                            throw new Exception("Ese libro xa est&aacute; dado de alta na base de datos...<br />");
                        default:
                            $id_libro = $fila['valor_devolto'];    // XA TEMOS O ID AUTONUMERICO DO LIBRO
                    }
                    $result->free();
                    while($conex->next_result() && $conex->store_result());
                }
                else{   // Sempre ten que devolver algo como temos implementado o proc.
                    throw new Exception('Houbo un erro na chamada á base de datos<br />');
                }
            }
            
            
            // Temos que dar de alta aos exemplares 
            $cont_num_exemplar=1;   // Número de exemplar
            foreach ($datos as $dato){
                $prezo = $conex->real_escape_string($dato['prezo']);
                $estado = $conex->real_escape_string($dato['estado']);
                $imaxe = $conex->real_escape_string($dato['imaxe']);
                $proc=sprintf(PROCEDEMENTO_INSERIR_EXEMPLAR,$id_libro,$cont_num_exemplar,$imaxe,$prezo,$estado);
                $proc = sprintf("CALL %s",$proc);
                if ($conex->multi_query($proc)){
                    $result = $conex->store_result();   // Neste caso o procedemento non devolve nada => vale 'false'
                    if ($conex->errno>0){
                        $error .= 'Houbo un erro na alta dos exemplares';
                        break;  //Saimos do for
                    }
                    while($conex->next_result() && $conex->store_result());
                }                   
                $cont_num_exemplar+=1;
            }   // Fin do For por cada exemplar

            $conex->commit();
    
        }   // 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) {
            if(!empt($conex)) $conex->rollback();
            $error=sprintf("error='Erro mysql: %s'",htmlentities($e->getMessage())); 
        }
        catch (Expcetion $e) {
            if(!empt($conex)) $conex->rollback();
            $error=sprintf("error='Erro xeral: %s'",htmlentities($e->getMessage()));
        }
        finally{
            if(!empty($conex)){
                $conex->close();
            }
        }            
        
    }   // Fin de (empty($error)
}

// *********************************FIN DE ALTA ************************************************************************//
?>


<!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>Transaccións</title>
    </head>

    <body>
        <?php
            $titulo='';
            $num_exemplares='';
            if (!empty($_POST['btnEnviar'])) {  // Prememos o botón que indica o título e o número de exemplares
                $titulo = filter_var($_POST['txtTitulo'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
                if (strlen($titulo)>200){   // Non pode ter unha lonxitude maior que o campo da base de datos
                    $error='A lonxitude do título non pode ser maior que 200 caracteres...<br />';
                }
                $num_exemplares = $_POST['txtNumExemplares'];
                if (!filter_var($num_exemplares,FILTER_VALIDATE_INT) || $num_exemplares > 9 || $num_exemplares < 1){
                    $error.="O número de exemplares ten que ser numérico e non pode ser maior que 9<br />";
                }
                if (strlen($titulo)==0) {   
                    $error.='O título é obrigatorio...<br />';
                }
            }
        ?>
        
        <?php
            $inhabilitar='';
            if (!empty($_POST['btnEnviar']) && (empty($error))) {   // Facemos que a caixa de texto e todo e demais quede inhabilitado 
                $inhabilitar="disabled='disabled'";                  // Unha vez enviamos os datos do titulo e num exemplares
            }
        ?>
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
            <div>Titulo Libro: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'",$titulo); ?> type='text' size='100' maxlength="300" name='txtTitulo' /></div>
            <div>Num. Exemplares: <input <?php echo $inhabilitar ?>  <?php printf(" value='%s'",$num_exemplares); ?> type='number' min='1' max='9' size="2" maxlength="1" name='txtNumExemplares' /> (&lt;10) </div>
            <input <?php echo $inhabilitar ?> type="submit" value='Enviar' name='btnEnviar' />    
        </form>

    
        
        <?php 
        if ($titulo && $num_exemplares){    // Enviamos datos
            // NON IMOS A FACER UNHA CONSULTA A TABOA ESTADOS POR CADA UN DOS EXEMPLARES. NON TEN SENTIDO.
            // PODERIAMOS PERCORRER O ARRAY DE RESULTADOS E AO REMATAR VOLVER AO PRINCIPIO: http://php.net/manual/es/mysqli-result.data-seek.php

            try {
                $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);   
                $conex->set_charset('utf8mb4');
            
                $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_ESTADOS);
                if ($conex->multi_query($proc)){
                    $result = $conex->store_result();
                    if ($result && ($result->num_rows>0)){
                        while($valor = $result->fetch_assoc()){
                            $estados[]=$valor;
                        }
                        $result->free();
                        while($conex->next_result() && $conex->store_result());
                    }
                }
            }   // 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("Erro mysql: %s",htmlentities($e->getMessage()));
            }
            catch (Expcetion $e) {
                $error.=sprintf("Erro xeral: %s",htmlentities($e->getMessage()));
            }
            finally{
                if(!empty($conex)){
                    $conex->close();
                }
            }

            printf("<form action=\"%s\" method='post'>",htmlentities($_SERVER['PHP_SELF']));
            printf("<input type='hidden' value='%s' name='txtTitulo' />",htmlentities($_POST['txtTitulo'])); // Como temos dous formularios, temos que gardar o texto do título nun campo oculto
            printf ("<table>");
            for($cont=0;$cont<$num_exemplares;$cont++){
                echo "<th>Estado</th><th>Imaxe</th><th>Prezo</th>";
                echo "<tr>";
                echo "<td>";    // ESTADO
                    printf ("<select name='lstEstado[]>");  // Poñemos o nome en forma de array xa que imos recibir varios exemplares con datos.
                    foreach ($estados as $estado){
                        printf("<option value='%d'>%s</option>",htmlentities($estado['id']),htmlentities($estado['descripcion']));
                    }
                    printf ("</select>");
                echo "</td>";
                echo "<td>";    // IMAXE
                    printf("<input type='text' size='50' maxlength='70' name='txtImaxe[]' />");
                echo "</td>";
                echo "<td>";    // PREZO
                    printf("<input type='text' size='9' maxlength='9' name='txtPrezo[]' /> Euros (XXXXXX,YY)");
                echo "</td>";
                echo "</tr>";
            }
            
            printf ("</table>");
            printf("<input type='submit' value='ALTA' name='btnAlta' />");    
            printf("<input type='reset' value='CANCELAR' name='btnCancelar' />");    
            printf("</form>");
            
        }
        ?>
        <?php
          if (!empty($error)) {
             printf("<h4>%s</h4>", $error);
          }
        ?>

    </body>    
</html>



Empregando PDO

Arquivo bd.php:

define('PROCEDEMENTO_INSERIR_LIBRO',"INSERIR_LIBRO(:titulo)");
define('PROCEDEMENTO_INSERIR_EXEMPLAR',"INSERIR_EXEMPLAR(:id_libro,:id_exemplar,:imaxe,:prezo,:estado)");
define('PROCEDEMENTO_CONSULTAR_ESTADOS','CONSULTAR_ESTADOS()');




Arquivo alta_libro_exemplares.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 

// *********************************ALTA ************************************************************************//
if (!empty($_POST['btnAlta'])) { // DAMOS DE ALTA A EXAMPLARES E O LIBRO
    foreach ($_POST['lstEstado'] as $indice => $estado) {    // Usamos o índice para buscar os datos no resto dos campos => Fixarse que é nu foreach
        $prezo = $_POST['txtPrezo'][$indice];
        $imaxe = $_POST['txtImaxe'][$indice];

        // Xa temos os datos de cada un dos exemplares
        // Verifacamos que cumpran os criterios de tipo e formato => PRIMEIRO VALIDAMOS. SE SON CORRECTOS TODOS INICIAMOS A OPERACION DE ALTA
        if (!filter_var($estado, FILTER_VALIDATE_INT) || $estado < 0) {
            $error .= "O estado é incorrecto...";
            break;
        }
        if (filter_var(str_replace(',', '.', $prezo), FILTER_VALIDATE_FLOAT) == false || $prezo <= 0 || !preg_match("/[0-9]{1,6}(,[0-9]{1,2})?/", $prezo)) {
            $error .= "O prezo é incorrecto...";
            break;
        }
        $imaxe = filter_var($imaxe, FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
        if (((strlen($imaxe)) > 100) || (strlen($imaxe) == 0)) {    // O número de caracteres está establecido na táboa da base de datos
            $error .= "O nome da imaxe é demasiado grande ou non foi posto...";
            break;
        }
        // Gardamos os datos validados para dalos de alta todos xuntos
        $dato['prezo'] = $prezo;
        $dato['imaxe'] = $imaxe;
        $dato['estado'] = $estado;
        $datos[] = $dato;
    }   // Fin do foreach

    if (empty($error)) {  // Non houbo error, empezamos a operación de alta con transaccións
        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);

            $conex->beginTransaction(); // INICIAMOS TRANSACCION

            $stmt = $conex->prepare("CALL " . PROCEDEMENTO_INSERIR_LIBRO);    // Temos que poñer prepare
            $stmt->bindValue(':titulo', $_POST['txtTitulo'], PDO::PARAM_STR);
            $result = $stmt->execute();

            // Damos de alta ao libro e obtemos o id xerado
            if ($result) {
                $fila = $stmt->fetch(PDO::FETCH_ASSOC);
                switch ($fila['valor_devolto']) {
                    case -1:
                        throw new Exception("Ese libro xa est&aacute; dado de alta na base de datos...<br />");
                    default:
                        $id_libro = $fila['valor_devolto'];    // XA TEMOS O ID AUTONUMERICO DO LIBRO
                }
                $stmt = null;
            } else {   // Sempre ten que devolver algo como temos implementado o proc.
                throw new Exception('Houbo un erro na chamada á base de datos<br />');
            }

            // Temos que dar de alta aos exemplares 
            $cont_num_exemplar = 1;   // Número de exemplar
            foreach ($datos as $dato) {

                $stmt = $conex->prepare("CALL " . PROCEDEMENTO_INSERIR_EXEMPLAR);    // Temos que poñer prepare
                $stmt->bindValue(':id_libro', $id_libro, PDO::PARAM_INT);
                $stmt->bindValue(':id_exemplar', $cont_num_exemplar, PDO::PARAM_INT);
                $stmt->bindValue(':imaxe', $dato['imaxe'], PDO::PARAM_STR);
                $stmt->bindValue(':prezo', str_replace(',', '.', $dato['prezo']), PDO::PARAM_STR); // NON EXISTE DOUBLE EN PARAM_ por iso temos que reemplazar a coma polo punto
                $stmt->bindValue(':estado', $dato['estado'], PDO::PARAM_INT);

                $result = $stmt->execute();
                if (!$result) {   // FIXARSE QUE NON LANZAMOS EXCEPCION => DEIXA DADOS DE ALTA ATA O MOMENTO DO ERRO
                    $error .= 'Houbo un erro na alta dos exemplares';
                    break;
                }

                $cont_num_exemplar += 1;
            }   // Fin do For por cada exemplar

            $conex->commit();
        }   // 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) {
            if (!empty($conex) && $conex->inTransaction()) {
                $conex->rollBack();
            }
            $error = sprintf("erro PDO:%s", $e->getMessage());
        } catch (Exception $e) {
            if (!empty($conex) && $conex->inTransaction()) {
                $conex->rollBack();
            }
            $error = sprintf("erro XERAL:%s", $e->getMessage());
        } finally {
            $conex = null; // Pechar a conexión
        }
    }   // Fin de (empty($error)
}

// *********************************FIN DE ALTA ************************************************************************//
?>

<!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>Transaccións</title>
</head>

<body>
    <?php
    $titulo = '';
    $num_exemplares = '';
    if (!empty($_POST['btnEnviar'])) {  // Prememos o botón que indica o título e o número de exemplares
        $titulo = filter_var($_POST['txtTitulo'], FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
        if (strlen($titulo) > 200) {   // Non pode ter unha lonxitude maior que o campo da base de datos
            $error = 'A lonxitude do título non pode ser maior que 200 caracteres...<br />';
        }
        $num_exemplares = $_POST['txtNumExemplares'];
        if (!filter_var($num_exemplares, FILTER_VALIDATE_INT) || $num_exemplares > 9 || $num_exemplares < 1) {
            $error .= "O número de exemplares ten que ser numérico e non pode ser maior que 9<br />";
        }
        if (strlen($titulo) == 0) {
            $error .= 'O título é obrigatorio...<br />';
        }
    }
    ?>

    <?php
    $inhabilitar = '';
    if (!empty($_POST['btnEnviar']) && (empty($error))) {   // Facemos que a caixa de texto e todo e demais quede inhabilitado 
        $inhabilitar = "disabled='disabled'";                  // Unha vez enviamos os datos do titulo e num exemplares
    }
    ?>
    <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
        <div>Titulo Libro: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'", $titulo); ?> type='text' size='100' maxlength="300" name='txtTitulo' /></div>
        <div>Num. Exemplares: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'", $num_exemplares); ?> type='number' min='1' max='9' size="2" maxlength="1" name='txtNumExemplares' /> (&lt;10) </div>
        <input <?php echo $inhabilitar ?> type="submit" value='Enviar' name='btnEnviar' />
    </form>


    <?php
    if ($titulo && $num_exemplares) {    // Enviamos datos
        // NON IMOS A FACER UNHA CONSULTA A TABOA ESTADOS POR CADA UN DOS EXEMPLARES. NON TEN SENTIDO.
        // PODERIAMOS PERCORRER O ARRAY DE RESULTADOS E AO REMATAR VOLVER AO PRINCIPIO: http://php.net/manual/es/mysqli-result.data-seek.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);

            $proc = sprintf("CALL %s", PROCEDEMENTO_CONSULTAR_ESTADOS);
            $result = $conex->query($proc);
            if ($result && $result->rowCount() > 0) {
                while ($fila = $result->fetch(PDO::FETCH_ASSOC)) {
                    $estados[] = $fila;
                }
                $result = null;
            }
        } catch (PDOException $e) {
            $error .= sprintf("Erro mysql: %s", htmlentities($e->getMessage()));
        } catch (Exception $e) {
            $error .= sprintf("Erro xeral: %s", htmlentities($e->getMessage()));
        } finally {
            $conex = null; // Pechar a conexión
        } // 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

        printf("<form action=\"%s\" method='post'>", htmlentities($_SERVER['PHP_SELF']));
        printf("<input type='hidden' value='%s' name='txtTitulo' />", htmlentities($_POST['txtTitulo'])); // Como temos dous formularios, temos que gardar o texto do título nun campo oculto
        printf("<table>");
        for ($cont = 0; $cont < $num_exemplares; $cont++) {
            echo "<th>Estado</th><th>Imaxe</th><th>Prezo</th>";
            echo "<tr>";
            echo "<td>";    // ESTADO
            printf("<select name='lstEstado[]>");  // Poñemos o nome en forma de array xa que imos recibir varios exemplares con datos.
            foreach ($estados as $estado) {
                printf("<option value='%d'>%s</option>", htmlentities($estado['id']), htmlentities($estado['descripcion']));
            }
            printf("</select>");
            echo "</td>";
            echo "<td>";    // IMAXE
            printf("<input type='text' size='50' maxlength='70' name='txtImaxe[]' />");
            echo "</td>";
            echo "<td>";    // PREZO
            printf("<input type='text' size='9' maxlength='9' name='txtPrezo[]' /> Euros (XXXXXX,YY)");
            echo "</td>";
            echo "</tr>";
        }

        printf("</table>");
        printf("<input type='submit' value='ALTA' name='btnAlta' />");
        printf("<input type='reset' value='CANCELAR' name='btnCancelar' />");
        printf("</form>");
    }
    ?>
    <?php
    if (!empty($error)) {
        printf("<h4>%s</h4>", $error);
    }
    ?>

</body>

</html>




  • Exercicio proposto
Tedes neste enlace da WIKI a forma de subir unha imaxe ao servidor. Modificade o código anterior para facelo.


  • Exercicio proposto
Amplía a funcionalidade da páxina anterior para poder asociar a os autores cos libros na mesma operación de alta.
Para iso fai que os autores se carguen nunha lista (<select con size en HTML>) ordenados alfabeticamente e que se poidan seleccionar varios.
En caso de que os autores non estean dados de alta, poderás premer nun enlace que abrirá unha xanela nova (sen o toolbar, botóns,...) e que permitirá dar de alta a un novo autor. Ao pechar a xanela deberán refrescarse os datos da lista para amosar o novo autor, mantendo os datos previamente introducidos.







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