Diferencia entre revisiones de «PHP UD3 OP CONSULTA»

De MediaWiki
Ir a la navegación Ir a la búsqueda
 
(No se muestran 34 ediciones intermedias del mismo usuario)
Línea 20: Línea 20:
  
  
 +
* '''CONSIDERACIÓNS:'''
 +
:* Crear un usuario/password con permisos de acceso a dita base de datos (sempre dádelle os mínimos permisos posibles, neste caso, serán os de SELECT/UPDATE/DELETE/INSERT/EXECUTE de toda a base de datos)
 +
:* Crear nun arquivo separado do sitio web todos os datos necesarios para conectarse á base de datos e onde van estar definidas as constantes coas ordes SQL, tal como [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Gardando_os_datos_de_conexi.C3.B3n_nun_lugar_seguro vimos nun punto anterior desta sección].
 +
:* Lembrar poñer as ordes para amosar os erros que se poidan dar. Nunha aplicación final esas ordes hai que quitalas.
 +
:* En Linux, lembrar dar permiso 644 (rw,r,r) aos arquivos php e cambiar o propietario a www-data:www-data. Nos cartafoles permisos 755 cando rematedes.
 +
:* Ao principio ides faiar en moitas cousas. Lembrade ter posto o display_errors como vimos nas seccións anteriores. Tamén axuda moito facer un echo da sentenza a enviar á base de datos antes de enviala para ver que estea todo ben (espazos en branco, comillas,...).
 +
:* Lembrar que tamén podedes activar o debugger como está indicado [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Habilitar_depuraci%C3%B3n_en_VS_Code nesta sección da wiki].
  
 +
 +
 +
<br />
  
 
=== Realización dunha consulta SELECT ===
 
=== Realización dunha consulta SELECT ===
Línea 166: Línea 176:
  
  
−
* Se realmente necesitamos coñecer o número de rexistros, realizaremos unha consulta específica empregando COUNT(*):
+
* [https://www.php.net/manual/es/pdostatement.fetchcolumn.php fetchColum] permite recuperar da primeira fila a columna indicada. Se non poñemos ningunha columna recuperar a primeira.
 +
: Por exemplo, se realmente necesitamos coñecer o número de rexistros, realizaremos unha consulta específica empregando COUNT(*) e como sabemos que vai devolver unha fila cunha columna podemos empregar fetchColumn:
 +
 
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
 
$stmt = $conex->query(
 
$stmt = $conex->query(
Línea 245: Línea 257:
  
 
<br />
 
<br />
 +
 
=== Recuperación de filas ===
 
=== Recuperación de filas ===
 
'''Paso 4: Agora necesitaremos percorrer dende o primeiro rexistro ata o último amosando o resultado.'''  
 
'''Paso 4: Agora necesitaremos percorrer dende o primeiro rexistro ata o último amosando o resultado.'''  
Línea 523: Línea 536:
 
'''Arquivo bd.php'''
 
'''Arquivo bd.php'''
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
 +
 +
define('CARTAFOL_IMAXES_WEB', 'http://localhost/imaxes/');
 +
 
define(
 
define(
 
'CONSULTAR_EXEMPLARES_POR_LIBRO',
 
'CONSULTAR_EXEMPLARES_POR_LIBRO',
Línea 535: Línea 551:
 
);
 
);
 
</syntaxhighlight>
 
</syntaxhighlight>
−
'''Código PHP'''
 
−
::<syntaxhighlight lang="php">
 
−
$libroId = filter_input(
 
−
INPUT_POST,
 
−
'lstLibros',
 
−
FILTER_VALIDATE_INT
 
−
);
 
−
if (
 
−
$libroId !== false &&
 
−
$libroId !== null &&
 
−
$libroId !== -1
 
−
) {
 
−
$stmt = $conex->prepare(
 
−
    CONSULTAR_EXEMPLARES_POR_LIBRO
 
−
);
 
  
−
$stmt->bindValue(
 
−
    ':libro_id',
 
−
    $libroId,
 
−
    PDO::PARAM_INT
 
−
);
 
  
−
$stmt->execute();
 
  
−
$fila = $stmt->fetch();
+
<br />
 +
* '''Código PHP'''
 +
::<syntaxhighlight lang="php">
 +
<html>
  
−
if ($fila !== false) {
+
<head>Páxina de exemplo
 +
</head>
  
−
    echo "<table border='1'>";
+
<body>
−
    echo '<tr>';
 
−
    echo '<th>Num. exemplar</th>';
 
−
    echo '<th>Imaxe</th>';
 
−
    echo '<th>Prezo</th>';
 
−
    echo '<th>Estado</th>';
 
−
    echo '</tr>';
 
  
−
     do {
+
     <?php
 +
    ini_set('display_errors', 1);
 +
    ini_set('display_startup_errors', 1);
 +
    error_reporting(E_ALL);
  
−
        echo '<tr>';
+
    require('/var/www/confBD/bd.php');
 +
    try {
  
−
         printf(
+
         $dsn = sprintf(
−
             '<td>%d</td>',
+
             'mysql:host=%s;dbname=%s;charset=utf8mb4',
−
             (int) $fila['id_exemplar']
+
             SERVIDOR,
 +
            BD
 
         );
 
         );
  
−
         printf(
+
         $opciones = [
−
             "<td><img alt='%s' src='%s' width='42' height='42'></td>",
+
             PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
−
             htmlspecialchars(
+
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
−
                $fila['imaxe'] ?? '',
+
             PDO::ATTR_EMULATE_PREPARES  => false
−
                ENT_QUOTES | ENT_SUBSTITUTE,
+
        ];
−
                'UTF-8'
+
 
−
             ),
+
        $conex = new PDO(
−
             htmlspecialchars(
+
             $dsn,
−
                CARTAFOL_IMAXES_WEB . ($fila['imaxe'] ?? ''),
+
             USUARIO,
−
                ENT_QUOTES | ENT_SUBSTITUTE,
+
            PASSWORD,
−
                'UTF-8'
+
             $opciones
−
             )
 
 
         );
 
         );
  
−
         printf(
+
         $libroId = filter_input(INPUT_POST,'lstLibros',FILTER_VALIDATE_INT);  // Como non temos feito a páxina de formulario na seguinte liña dou un valor específico...
−
            '<td>%.2f</td>',
+
        $libroId = 1;  // Este dato virá dunha caixa de texto dunha páxina anterior que terá como action do formulario esta páxina (pode facerse todo nunha mesma páxina)
−
             (float) ($fila['prezo'] ?? 0)
+
 
−
        );
+
        if (
 +
            $libroId !== false &&
 +
            $libroId !== null &&
 +
            $libroId !== -1
 +
        ) {
 +
            $stmt = $conex->prepare(
 +
                CONSULTAR_EXEMPLARES_POR_LIBRO
 +
             );
 +
 
 +
            $stmt->bindValue(
 +
                ':libro_id',
 +
                $libroId,
 +
                PDO::PARAM_INT
 +
            );
 +
 
 +
            $stmt->execute();
  
−
        printf(
+
             $fila = $stmt->fetch();
−
             '<td>%d</td>',
 
−
            (int) $fila['estado_id']
 
−
        );
 
  
−
        echo '</tr>';
+
            if ($fila !== false) {
  
−
    } while ($fila = $stmt->fetch());
+
                echo "<table border='1'>";
 +
                echo '<tr>';
 +
                echo '<th>Num. exemplar</th>';
 +
                echo '<th>Imaxe</th>';
 +
                echo '<th>Prezo</th>';
 +
                echo '<th>Estado</th>';
 +
                echo '</tr>';
  
−
    echo '</table>';
+
                do {
  
−
} else {
+
                    echo '<tr>';
  
−
    echo 'Non hai datos que amosar';
+
                    printf(
 +
                        '<td>%d</td>',
 +
                        (int) $fila['id_exemplar']
 +
                    );
  
−
}
+
                    printf(
−
}
+
                        "<td><img alt='%s' src='%s' width='42' height='42'></td>",
−
</syntaxhighlight>
+
                        htmlspecialchars(
 +
                            $fila['imaxe'] ?? '',
 +
                            ENT_QUOTES | ENT_SUBSTITUTE,
 +
                            'UTF-8'
 +
                        ),
 +
                        htmlspecialchars(
 +
                            CARTAFOL_IMAXES_WEB . ($fila['imaxe'] ?? ''),
 +
                            ENT_QUOTES | ENT_SUBSTITUTE,
 +
                            'UTF-8'
 +
                        )
 +
                    );
  
−
== Procedementos almacenados empregando PDO ==
+
                    printf(
−
Os procedementos almacenados execútanse mediante a sentenza SQL CALL.
+
                        '<td>%.2f</td>',
−
: Por exemplo:
+
                        (float) ($fila['prezo'] ?? 0)
−
::<syntaxhighlight lang="php">
+
                    );
 +
 
 +
                    printf(
 +
                        '<td>%d</td>',
 +
                        (int) $fila['estado_id']
 +
                    );
 +
 
 +
                    echo '</tr>';
 +
                } while ($fila = $stmt->fetch());
 +
 
 +
                echo '</table>';
 +
            } else {
 +
 
 +
                echo 'Non hai datos que amosar';
 +
            }
 +
        }
 +
    } catch (PDOException $e) {
 +
        die("Error PDO: " . $e->getMessage());
 +
    } catch (Throwable $e) {
 +
        die("Error general: " . $e->getMessage());
 +
    }
 +
 
 +
    $conex = null;  // Poderíamos non poñer nada
 +
    ?>
 +
</body>
 +
 
 +
</html>
 +
</syntaxhighlight>
 +
 
 +
== Procedementos almacenados empregando PDO ==
 +
Os procedementos almacenados execútanse mediante a sentenza SQL CALL.
 +
: Por exemplo:
 +
::<syntaxhighlight lang="php">
 
$stmt = $conex->prepare(
 
$stmt = $conex->prepare(
 
'CALL CONSULTAR_LIBROS()'
 
'CALL CONSULTAR_LIBROS()'
Línea 793: Línea 853:
 
</syntaxhighlight>
 
</syntaxhighlight>
 
: Esta consulta devolvería ata 10 rexistros empezando desde o rexistro situado na posición 20.
 
: Esta consulta devolvería ata 10 rexistros empezando desde o rexistro situado na posición 20.
−
=== Número total de rexistros ===
+
 
 +
 
 +
 
 +
<br />
 +
'''Número total de rexistros'''
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
 
$stmt = $conex->query(
 
$stmt = $conex->query(
Línea 801: Línea 865:
 
$numLibros = (int) $stmt->fetchColumn();
 
$numLibros = (int) $stmt->fetchColumn();
 
</syntaxhighlight>
 
</syntaxhighlight>
−
=== Páxina actual ===
+
 
 +
 
 +
 
 +
<br />
 +
'''Páxina actual'''
 
O número de páxina recibido mediante GET é un dato externo e debe validarse.
 
O número de páxina recibido mediante GET é un dato externo e debe validarse.
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
Línea 818: Línea 886:
 
</syntaxhighlight>
 
</syntaxhighlight>
 
: htmlspecialchars() non serve para validar que un dato sexa un número. Empregarase posteriormente cando teñamos que inserir unha cadea dentro de HTML.
 
: htmlspecialchars() non serve para validar que un dato sexa un número. Empregarase posteriormente cando teñamos que inserir unha cadea dentro de HTML.
−
=== Cálculo do número de páxinas ===
+
 
 +
 
 +
<br />
 +
'''Cálculo do número de páxinas'''
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
 
define(
 
define(
Línea 837: Línea 908:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
−
=== Calcular o desprazamento ===
+
 
 +
 
 +
<br />
 +
'''Calcular o desprazamento'''
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
 
$inicio = (
 
$inicio = (
Línea 849: Línea 923:
 
:* Na páxina 3 => inicio = 20
 
:* Na páxina 3 => inicio = 20
  
−
=== Consulta paxinada ===
+
 
 +
<br />
 +
'''Consulta paxinada'''
 
Neste caso indicaremos explicitamente que os parámetros son enteiros:
 
Neste caso indicaremos explicitamente que os parámetros son enteiros:
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
Línea 901: Línea 977:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
=== Enlaces da paxinación ===
+
 
 +
 
 +
 
 +
<br />
 +
'''Enlaces da paxinación'''
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
 
echo '<div>';
 
echo '<div>';
Línea 927: Línea 1007:
 
echo '</div>';
 
echo '</div>';
 
</syntaxhighlight>
 
</syntaxhighlight>
−
=== Sobre a optimización do número total de rexistros ===
+
 
 +
 
 +
 
 +
<br />
 +
'''Sobre a optimización do número total de rexistros'''
 
En xeral non deberiamos enviar o número total de rexistros na URL confiando despois nese valor:
 
En xeral non deberiamos enviar o número total de rexistros na URL confiando despois nese valor:
 
::<syntaxhighlight lang="text">
 
::<syntaxhighlight lang="text">
Línea 940: Línea 1024:
 
</syntaxhighlight>
 
</syntaxhighlight>
 
: Se nunha aplicación real esta operación chegase a ser un problema de rendemento, poderían estudarse posteriormente técnicas de caché ou outras estratexias de optimización.
 
: Se nunha aplicación real esta operación chegase a ser un problema de rendemento, poderían estudarse posteriormente técnicas de caché ou outras estratexias de optimización.
 +
 
== Xestión de excepcións ==
 
== Xestión de excepcións ==
−
Nos exemplos completos empregaremos o seguinte esquema:
+
 
 +
 
 +
* Nos exemplos completos empregaremos o seguinte esquema:
 
::<syntaxhighlight lang="php">
 
::<syntaxhighlight lang="php">
 
try {
 
try {
−
// Conexión e operacións coa base de datos
+
    $conex = new PDO($dsn, $usuario, $contrasinal, [
 +
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
 +
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
 +
    ]);
 +
 
 +
    // Operacións coa base de datos...
 +
 
 
} catch (PDOException $e) {
 
} catch (PDOException $e) {
−
/*
+
    // Rexistrar o erro nun log (monolog, etc.)
−
* Durante o desenvolvemento podemos consultar
+
    error_log($e->getMessage());
−
* $e->getMessage().
+
   
−
*
+
    die('Erro relacionado coa base de datos.');
−
* Nunha aplicación en produción non debemos
 
−
* amosar esta información ao usuario.
 
−
*/
 
−
die(
 
−
    'Erro relacionado coa base de datos.'
 
−
);
 
 
} catch (Throwable $e) {
 
} catch (Throwable $e) {
−
die(
+
    die('Produciuse un erro na aplicación.');
−
    'Produciuse un erro na aplicación.'
 
−
);
 
−
} finally {
 
−
$conex = null;
 
 
}
 
}
 +
 +
// Non fai falta "finally {$conex = null;}":
 +
// Se o script continúa, destruirasie cando cumpra o seu ciclo;
 +
// se entra nos catch, o die() xa finaliza o proceso e libera a memoria.
 
</syntaxhighlight>
 
</syntaxhighlight>
−
PDOException permite tratar especificamente os erros de PDO.
+
: PDOException permite tratar especificamente os erros de PDO.
−
Throwable permite capturar outros erros ou excepcións producidos durante a execución.
+
: Throwable permite capturar outros erros ou excepcións producidos durante a execución.
−
Os bloques catch deben colocarse desde o máis específico ao máis xeral.
+
: Os bloques catch deben colocarse desde o máis específico ao máis xeral.
−
=== Caso Práctico 1 ===
 
−
: Imos crear unha consulta que nos devolva todos os libros da táboa LIBROS dentro dunha táboa:
 
−
: Para non ter as consultas no medio do código PHP das diferentes páxinas, imos definila no '''arquivo bd.php''' creado no punto anterior.
 
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
 
−
</syntaxhighlight>
 
  
  
−
* '''Normas nos nomes das constantes''':
+
<br />
−
:* Comenzarán cun verbo que vai poder ser: CONSULTAR, INSERIR, MODIFICAR e ELIMINAR
+
== Exercicios prácticos ==
−
:* Seguirá cun guión baixo.
 
−
:* En caso de consultar os datos dunha única táboa se indicará o nome da táboa. En caso de consultar datos de varias táboas se indicará con varias palabras separadas con guión baixo que indiquen o obxectivo da consulta.
 
  
 +
* Partimos de que xa temos creada a base de datos nunha [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Operacións_BD_Mysql#Creando_as_t.C3.A1boas_en_Mysql sección anterior desta wiki] e gardados os datos necesarios para realizar a conexión no arquivo `bd.php`.
  
 +
* '''IMPORTANTE:''' Nos exames, exercicios e prácticas avaliables empregaremos '''PDO''' para realizar as operacións contra a base de datos, salvo que no enunciado se indique expresamente o contrario.
  
 +
* Os exemplos con `mysqli` poden consultarse como información adicional, pero os exemplos de referencia dos apuntes serán os realizados mediante PDO.
  
−
* Aplicado ao exemplo que estamos a seguir...
+
* Nos exemplos completos empregaremos o seguinte esquema:
−
:<u>Nota</u> Lembrar activar as directivas para amosar erros e quitalas cando rematedes a páxina.
 
  
−
==== Solución Caso Práctico 1 Orientado a Obxectos====
+
: No arquivo bd.php que se atopa fora da ruta /var/www/html gardaremos os datos da conexión e as consultar a empregar:
−
 
+
::<syntaxhighlight lang="php">
−
'''Arquivo: consultar_libros.php'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="27-40" >
 
 
<?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');
+
define('SERVIDOR', 'localhost');
 +
define('USUARIO', 'user_php');
 +
define('PASSWORD', 'user_php');
 +
define('BD', 'php');
  
−
try {
+
define(
−
     $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD);  // Non indicamos o nome da bd
+
     'NOME_DA_CONSULTA',
−
     $conex->set_charset('utf8mb4');
+
     'SELECT col1,col2,...
−
    $conex->select_db(BD); // O parámetro indica o nome da base de datos
+
    FROM taboa
 +
    ORDER BY col1'
 +
);
 +
</syntaxhighlight>
  
−
?>
 
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
Nas páxinas PHP que van acceder á base de datos terán este esquema:
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
::<syntaxhighlight lang="php">
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
+
try {
−
    <head>
+
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+
require '/var/www/confBD/bd.php';
−
        <title>Operacións BD</title>
+
 
−
    </head>
+
try {
  
−
     <body>
+
     $dsn = sprintf(
−
    <?php       
+
         'mysql:host=%s;dbname=%s;charset=utf8mb4',
−
       
+
         SERVIDOR,
−
        $result=$conex->query(CONSULTAR_LIBROS);
+
         BD
−
         if ($result && $result->num_rows>0){
+
    );
−
            echo "<table border='1'>";
 
−
            while($row = $result->fetch_assoc()) {
 
−
                echo '<tr>';
 
−
                printf("<td>%s</td>",htmlentities($row["titulo"]));
 
−
                echo '</tr>';
 
−
            }
 
−
            echo '</table>';
 
−
            $result->free();  // Liberamos da memoria os recursos
 
−
         }
 
−
         else {
 
−
            echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
       
 
−
    ?>       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
    $conex = new PDO(
−
}    // Pecha o try do principio da páxina
+
        $dsn,
−
catch (mysqli_sql_exception $e) {
+
        USUARIO,
−
    die("Erro mysql: " . $e->getMessage());
+
        PASSWORD,
−
}
+
        [
−
catch (Expcetion $e) {
+
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
−
    die("Erro xeral: " . $e->getMessage());
+
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
−
}
+
        ]
−
finally{
+
    );
−
  if(!empty($conex)){
+
 
−
      $conex->close();
+
    // Operacións coa base de datos...
−
  }
+
 
−
}
+
 
−
?>
+
} catch (PDOException $e) {
 +
 
 +
 
 +
// Rexistrar o erro nun log.
 +
// Nunha aplicación real tamén poderían empregarse
 +
// ferramentas como Monolog.
 +
error_log($e->getMessage());
 +
 
 +
die('Erro relacionado coa base de datos.');
  
−
</syntaxhighlight>
+
} catch (Throwable $e) {
  
 +
die('Produciuse un erro na aplicación.');
  
−
* Resultado:
+
} </syntaxhighlight>
−
[[Imagen:Php_BD_8.jpg|300px|center]]
 
  
 +
:* `PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC` fai que, por defecto, as filas recuperadas mediante `fetch()` ou `fetchAll()` sexan arrays asociativos.
 +
:* `PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION` fai que os erros producidos por PDO xeren unha excepción de tipo `PDOException`.
 +
:* `PDOException` permítenos tratar especificamente os erros relacionados con PDO.
 +
:* `Throwable` permite capturar outros erros ou excepcións producidos durante a execución da aplicación.
 +
:* Os bloques `catch` deben ordenarse desde o máis específico ao máis xeral.
 +
:* Nunha aplicación en produción non debemos amosar ao usuario a mensaxe devolta por `$e->getMessage()`. Podemos rexistrala mediante `error_log()` ou outro sistema de logs.
  
−
* Fixarse como neste exemplo, aínda que a consulta devolve o id do libro, como non o necesitamos non o amosamos ao usuario, xa que dito dato é un código interno que utilizamos como clave primaria.
+
<br />
  
 +
=== Caso Práctico 1 ===
  
 +
* Imos crear unha consulta que devolva todos os libros da táboa `LIBROS` e os amose dentro dunha táboa HTML.
  
−
* O método fetch_assoc() move un punteiro interno á seguinte fila de resultados.
+
* Para non ter as consultas SQL no medio do código das diferentes páxinas, definirémolas no arquivo `bd.php`.
−
: Se queremos modificar dito punteiro e situarnos en calquera fila do resultado, debemos facer uso da [http://php.net/manual/es/mysqli-result.data-seek.php función mysqli_data_seek]·
 
  
 +
'''Arquivo: bd.php'''
  
 +
::<syntaxhighlight lang="php">
 +
define(
 +
'CONSULTAR_LIBROS',
 +
'SELECT id, titulo
 +
FROM libros
 +
ORDER BY titulo'
 +
); </syntaxhighlight>
  
 +
* Como norma para os nomes das constantes que conteñan sentenzas SQL:
  
 +
:* Comezarán cun verbo que poderá ser `CONSULTAR`, `INSERIR`, `MODIFICAR` ou `ELIMINAR`.
 +
:* A continuación empregarase un guión baixo.
 +
:* Despois indicarase o obxectivo da operación.
  
−
<br />
+
==== Solución Caso Práctico 1 empregando PDO ====
−
==== Solución Caso Práctico 1 empregando PDO====
 
  
 
'''Arquivo: consultar_libros.php'''
 
'''Arquivo: consultar_libros.php'''
−
::<syntaxhighlight lang="php" enclose="div" highlight="27-40" >
+
 
 +
::<syntaxhighlight lang="php">
 +
 
 
<?php
 
<?php
−
/* Lembrar quitar isto ao probar o scripts sen erros */
+
 
−
ini_set('display_errors', 1);
+
ini_set('display_errors', '1');
−
ini_set('display_startup_errors', 1);
 
 
error_reporting(E_ALL);
 
error_reporting(E_ALL);
  
−
require ('/var/www/confBD/bd.php');
+
require '/var/www/confBD/bd.php';
  
 
try {
 
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);
 
  
−
?>
+
    $dsn = sprintf(
 +
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
 +
        SERVIDOR,
 +
        BD
 +
    );
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
    $conex = new PDO(
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
        $dsn,
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
+
        USUARIO,
−
    <head>
+
        PASSWORD,
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+
        [
−
         <title>Operacións BD</title>
+
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
−
     </head>
+
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
 +
         ]
 +
     );
  
−
     <body>
+
     $stmt = $conex->query(CONSULTAR_LIBROS);
−
    <?php       
 
−
       
 
−
        $result=$conex->query(CONSULTAR_LIBROS);
 
−
        // Comprobar se hai filas
 
−
        if ($result && $result->rowCount() > 0) {
 
−
            echo "<table border='1'>";
 
−
            while ($fila = $result->fetch(PDO::FETCH_ASSOC)) {
 
−
                echo '<tr>';
 
−
                printf("<td>%s</td>",htmlentities($fila["titulo"]));
 
−
                echo '</tr>';
 
−
            }
 
−
            echo '</table>';
 
−
            $result = null; 
 
−
        } else {
 
−
          echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
     
 
−
    ?>       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
    $libros = $stmt->fetchAll();
−
}    // Pecha o try do principio da páxina
 
−
catch (PDOException $e) {
 
−
  die("Erro PDO: " . $e->getMessage());
 
−
} finally {
 
−
  $conex = null; // Pechar a conexión
 
−
}
 
  
−
?>
+
} catch (PDOException $e) {
  
−
</syntaxhighlight>
+
    error_log($e->getMessage());
 +
    die('Erro relacionado coa base de datos.');
  
 +
} catch (Throwable $e) {
  
−
* Resultado:
+
    die('Produciuse un erro na aplicación.');
−
[[Imagen:Php_BD_8.jpg|300px|center]]
 
  
 +
}
  
−
* Fixarse como neste exemplo, aínda que a consulta devolve o id do libro, como non o necesitamos non o amosamos ao usuario, xa que dito dato é un código interno que utilizamos como clave primaria.
+
?>
  
 +
<!DOCTYPE html>
  
−
<br />
+
<html lang="gl">
 +
<head>
 +
    <meta charset="UTF-8">
 +
    <title>Operacións BD</title>
 +
</head>
  
−
=== Caso Práctico 2 ===
+
<body>
  
−
* Agora imos facer unha consulta á táboa AUTORES recuperando todos os autores ordenados por apelidos e os amosaremos en forma de lista desordenada no que cada elemento da lista será enlace có seguinte formato: <enlace href='detalle_autor.php?id=XXX'>Apelidos , nome </enlace>
+
<?php
−
: Sendo XXX a columna id_autor.
 
  
−
: <u>Nota:</u> Se queremos facer máis seguro o paso de información entre páxinas, poderíamos empregar un formulario e que cada autor tivera un button con un value igual á clave primaria. De tal forma que ao premer nel, o formulario envaría á seguinte páxina o id do autor premido e dita información non ser vería na URL. Por exemplo: <button type="submit" name="enviar" value="valor_a_enviar">Texto a amosar</button>
+
if ($libros) {
  
 +
    echo "<table border='1'>";
  
 +
    foreach ($libros as $libro) {
  
 +
        echo '<tr>';
  
−
<br />
+
        printf(
−
==== Solución Caso Práctico 2 orientado a obxectos====
+
            '<td>%s</td>',
 +
            htmlspecialchars(
 +
                $libro['titulo'],
 +
                ENT_QUOTES | ENT_SUBSTITUTE,
 +
                'UTF-8'
 +
            )
 +
        );
  
−
: No '''arquivo bd.php''' creado no punto anterior.
+
        echo '</tr>';
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
    }
−
define ('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
+
 
−
</syntaxhighlight>  
+
    echo '</table>';
 +
 
 +
} else {
  
 +
    echo 'Non hai datos que amosar.';
  
 +
}
  
−
<br />
+
?>
−
'''Páxina consultar_autores.php'''
 
−
:<syntaxhighlight lang="php" enclose="div" highlight="26-40" >
 
−
<?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');
+
</body>
 +
</html>
 +
</syntaxhighlight>
  
−
try {
+
* Neste caso empregamos `query()` porque a sentenza SQL non recibe ningún dato externo.
−
    $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);    // Indico nome da bd
 
−
    $conex->set_charset('utf8mb4');
 
  
−
?>
+
* `fetchAll()` recupera todas as filas do resultado.
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
: Esta solución é adecuada cando sabemos que o número de filas que imos recuperar é pequeno.
−
    "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>Operacións BD</title>
 
−
    </head>
 
  
−
    <body>
+
* Se o resultado puidese ter moitas filas, sería preferible procesalas unha a unha:
−
    <?php      
+
 
−
       
+
::<syntaxhighlight lang="php">
−
        $result=$conex->query(CONSULTAR_AUTORES);
+
while ($libro = $stmt->fetch()) {
−
        if ($result && $result->num_rows>0){
 
−
            // Veñen datos
 
−
            echo "<ul>";
 
−
            while($row = mysqli_fetch_assoc($result)) {
 
−
                echo "<li>";
 
−
                printf("<a href='detalle_autor.php?id=%s'>%s</a>",$row["id"],urlencode($row["apelidos"]) .', '. urlencode($row['nome']));
 
−
                echo "</li>";
 
−
            }
 
−
            echo "</ul>";
 
−
            $result->free();  // Liberamos da memoria os recursos
 
−
        }
 
−
        else {
 
−
            echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
       
 
−
    ?>       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
echo htmlspecialchars(
−
}
+
     $libro['titulo'],
−
catch (mysqli_sql_exception $e) {
+
    ENT_QUOTES | ENT_SUBSTITUTE,
−
     die("Erro mysql: " . $e->getMessage());
+
     'UTF-8'
−
}
+
);
−
catch (Expcetion $e) {
 
−
     die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
 +
} </syntaxhighlight>
  
 +
* '''IMPORTANTE:''' Non empregaremos `rowCount()` para comprobar cantas filas devolveu unha sentenza `SELECT`.
  
 +
: Se realmente necesitamos coñecer o número de rexistros, empregaremos `COUNT(*)`.
  
 
<br />
 
<br />
−
==== Solución Caso Práctico 2 empregando PDO====
 
  
−
: No '''arquivo bd.php''' creado no punto anterior.
+
=== Caso Práctico 2 ===
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
 
−
define ('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
+
* Imos recuperar todos os autores ordenados polos seus apelidos e nome.
−
</syntaxhighlight>
+
 
 +
* Cada autor aparecerá como un enlace co seguinte formato:
  
 +
::<syntaxhighlight lang="html"> <a href="detalle_autor.php?id=5">
 +
Apelidos, Nome </a> </syntaxhighlight>
  
 +
* O identificador será enviado mediante a URL.
  
−
<br />
+
: Que un identificador apareza na URL '''non supón por si mesmo un problema de seguridade'''.
−
'''Páxina consultar_autores.php'''
 
−
:<syntaxhighlight lang="php" enclose="div" highlight="27-41" >
 
−
<?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');
+
: O importante é validar sempre o valor recibido na páxina de destino e comprobar que o usuario ten permiso para acceder ao recurso correspondente.
  
−
try {
+
'''Arquivo: bd.php'''
−
    $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);
 
  
−
?>
+
::<syntaxhighlight lang="php">
 +
define(
 +
'CONSULTAR_AUTORES',
 +
'SELECT id, apelidos, nome
 +
FROM autores
 +
ORDER BY apelidos, nome'
 +
); </syntaxhighlight>
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
==== Solución Caso Práctico 2 empregando PDO ====
−
    "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>Operacións BD</title>
 
−
    </head>
 
  
−
    <body>
+
'''Arquivo: consultar_autores.php'''
−
    <?php       
 
−
       
 
−
        $result=$conex->query(CONSULTAR_AUTORES);
 
−
        // Comprobar se hai filas
 
−
        if ($result && $result->rowCount() > 0) {
 
−
            echo "<ul>";
 
−
            while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
 
−
                echo "<li>";
 
−
                printf("<a href='detalle_autor.php?id=%s'>%s</a>",$row["id"],urlencode($row["apelidos"]) .', '. urlencode($row['nome']));
 
−
                echo "</li>";
 
−
            }
 
−
            echo "</ul>";
 
  
−
            $result = null; 
+
::<syntaxhighlight lang="php">
−
        } else {
 
−
          echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
     
 
−
    ?>       
 
−
    </body>   
 
−
</html>
 
  
 
<?php
 
<?php
−
}    // Pecha o try do principio da páxina
 
−
catch (PDOException $e) {
 
−
  die("Erro PDO: " . $e->getMessage());
 
−
} finally {
 
−
  $conex = null; // Pechar a conexión
 
−
}
 
  
−
?>
+
ini_set('display_errors', '1');
−
</syntaxhighlight>
+
error_reporting(E_ALL);
  
 +
require '/var/www/confBD/bd.php';
  
 +
try {
  
 +
    $dsn = sprintf(
 +
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
 +
        SERVIDOR,
 +
        BD
 +
    );
  
−
<br />
+
    $conex = new PDO(
 +
        $dsn,
 +
        USUARIO,
 +
        PASSWORD,
 +
        [
 +
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
 +
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
 +
        ]
 +
    );
  
−
=== Consulta enviando parámetros de tipo numérico ===
+
    $stmt = $conex->query(CONSULTAR_AUTORES);
  
 +
    $autores = $stmt->fetchAll();
  
−
====Orientado a obxectos====
+
} catch (PDOException $e) {
−
<u>Nota:</u> Lembra que no paso de datos temos que ter en conta os posibles 'ataques' que poidan intentar facerse. Repasar o dado [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Seguridade_no_uso_dos_formularios na wiki anteriormente].
 
  
−
: Neste curso '''sempre''' que se vaia a enviar un dato ao xestor Mysql aplicarémoslle a [https://www.php.net/manual/es/mysqli.real-escape-string.php función mysqli_real_escape_string]. O ideal sería empregar procedementos almacenados como veremos posteriormente.
+
    error_log($e->getMessage());
 +
    die('Erro relacionado coa base de datos.');
  
 +
} catch (Throwable $e) {
  
−
* Cando facemos unha consulta ou operación contra base de datos (alta, baixa e modificación) imos ter que 'enviar' datos.
+
    die('Produciuse un erro na aplicación.');
−
: A forma de facelo é utilizando a consulta SQL cun parámetro e substituíndo dito parámetro polo valor que queiramos enviar.
 
  
−
* Así: SELECT col1, col2 FROM TABOA_1 WHERE col1='''%d'''
+
}
  
−
: No exemplo, col1 é de tipo numérico.
+
?>
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CONSULTA_CON_PARAM_NUMERICO','SELECT col1, col2 FROM TABOA_1 WHERE col1=%d');
 
  
−
$query = sprintf(CONSULTA_CON_PARAM_NUMERICO,valor_numérico_a_enviar);
+
<!DOCTYPE html>
−
</syntaxhighlight>  
 
  
−
: Agora facemos o mesmo código que no exemplo anterior.
+
<html lang="gl">
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
<head>
−
......
+
    <meta charset="UTF-8">
−
$result=$conex->query(CONSULTA_CON_PARAM_NUMERICO);
+
    <title>Autores</title>
−
......
+
</head>
−
</syntaxhighlight>  
 
  
 +
<body>
  
 +
<?php
  
−
<br />
+
if ($autores) {
−
====Empregando PDO====
 
  
−
* No caso de PDO, non necesitamos empregar a función [https://www.php.net/manual/es/mysqli.real-escape-string.php función mysqli_real_escape_string] para evitar ataques que poidan facerse.
+
    echo '<ul>';
−
: O proceso de envía de parámetros en PDO faise dunha forma moi parecido a o envío de parámetros empregando procedementos almacenados de Mysql (o veremos despois).
 
  
−
* Cando facemos unha consulta ou operación contra base de datos (alta, baixa e modificación) imos ter que 'enviar' datos.
+
    foreach ($autores as $autor) {
−
: A forma de facelo é utilizando a consulta SQL cun parámetro e substituíndo dito parámetro polo valor que queiramos enviar.
 
  
−
* Así: SELECT col1, col2 FROM TABOA_1 WHERE col1= ''':valor'''
+
        printf(
 +
            "<li><a href='detalle_autor.php?id=%d'>%s, %s</a></li>",
 +
            (int) $autor['id'],
 +
            htmlspecialchars(
 +
                $autor['apelidos'],
 +
                ENT_QUOTES | ENT_SUBSTITUTE,
 +
                'UTF-8'
 +
            ),
 +
            htmlspecialchars(
 +
                $autor['nome'],
 +
                ENT_QUOTES | ENT_SUBSTITUTE,
 +
                'UTF-8'
 +
            )
 +
        );
 +
    }
  
−
: No exemplo, col1 é de tipo numérico.
+
    echo '</ul>';
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CONSULTA_CON_PARAM_NUMERICO','SELECT col1, col2 FROM TABOA_1 WHERE col1= :valor1');
 
−
// define ('CONSULTA_CON_PARAM_NUMERICO','SELECT col1, col2 FROM TABOA_1 WHERE col1= ?');  => Tamén vale con poñer unha interrogación
 
  
−
// Preparar a consulta
+
} else {
−
$stmt = $conex->prepare(CONSULTA_CON_PARAM_NUMERICO);
 
  
−
// Executar a consulta substituíndo o parámetro polo valor que queremos enviar
+
    echo 'Non hai datos que amosar.';
−
$stmt->execute(['valor' => 5]);
 
−
// Se emprego interrogación se cambia por orde. Se houbera 2 interrogacións, ao chamar a execute([5,7]) a primeira interrogación tería de valor 5 e a segunda 7.
 
−
// $stmt->execute([5]);
 
  
−
</syntaxhighlight>
+
}
  
−
: Tamén podemos indicar doutra forma o tipo de datos a enviar. Neste tipo de consulta non é necesario xa que vai facer a conversión no execute e mysql o acepta, pero pode ser que noutros xestores a comprobación de tipos sexa máis estricta e nese caso necesitaredes envialo desta forma:
+
?>
−
::<syntaxhighlight lang="php" enclose="div" highlight="5" >
 
−
define ('CONSULTA_CON_PARAM_NUMERICO','SELECT col1, col2 FROM TABOA_1 WHERE col1= :valor1');
 
  
−
// Preparar a consulta
+
</body>
−
$stmt = $conex->prepare(CONSULTA_CON_PARAM_NUMERICO);
+
</html>
−
$stmt->bindValue(':valor', 5, PDO::PARAM_INT);  // Enviamos o valor 5 de tipo int.
+
</syntaxhighlight>
  
−
// Executar a consulta substituíndo o parámetro polo valor que queremos enviar
+
* Para amosar texto dentro de HTML empregamos `htmlspecialchars()`.
−
$stmt->execute();
 
  
−
</syntaxhighlight>
+
* Non debemos empregar `urlencode()` sobre o nome e os apelidos, xa que estes valores non forman parte da URL senón do texto visible da páxina.
  
−
: Un exemplo de tipo é o boolean.
+
<br />
−
: Se a base de datos Mysql vai recoller o dato nun tipo numérico, mysql convirte true a 1 e false a 0.
 
−
: Pero noutras bases de datos pode dar un erro polo que deberíamos facelo desta forma:
 
  
 +
=== Consulta enviando parámetros de tipo numérico ===
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
* Cando unha consulta necesita recibir datos externos empregaremos unha '''sentenza preparada''' mediante:
−
$activo = true;
 
−
$stmt->bindValue(':activo', $activo, PDO::PARAM_BOOL);
 
−
$result = $stmt->execute();
 
−
</syntaxhighlight>
 
  
 +
::<syntaxhighlight lang="php">
 +
prepare()
 +
execute() </syntaxhighlight>
  
 +
* Por exemplo:
  
 +
::<syntaxhighlight lang="sql">
 +
SELECT col1, col2
 +
FROM TABOA_1
 +
WHERE col1 = :valor </syntaxhighlight>
  
−
<br />
+
* Definimos a consulta:
  
−
==== Caso práctico: Enviando parámetros de tipo numérico ====
+
::<syntaxhighlight lang="php">
 +
define(
 +
'CONSULTA_CON_PARAM_NUMERICO',
 +
'SELECT col1, col2
 +
FROM TABOA_1
 +
WHERE col1 = :valor'
 +
); </syntaxhighlight>
  
−
* Seguindo con exemplo, imos facer que cando o usuario seleccione un libro dentro dunha combobox, aparezan os exemplares do mesmo.
+
* Podemos executala desta forma:
−
: Para iso teremos que:
 
−
:* Crear a sentencia SQL correspondente enviando un parámetro que vai ser o código do libro seleccionado.
 
−
:* Obter dito dato. Isto o faremos utilizando un formulario cun método POST.
 
−
:* Modificar o código da páxina anterior (Consultar_Libros.php) e facer que os libros aparezan nunha combobox (dropdownlist ou <select> en html).
 
−
:* Facer que cando o usuario escolla un libro 'active' o formulario e polo tanto provoque o 'submit' do mesmo amosando os exemplares do libro seleccionado.
 
−
:* Manter seleccionado o libro na combo.
 
  
 +
::<syntaxhighlight lang="php">
 +
$stmt = $conex->prepare(
 +
CONSULTA_CON_PARAM_NUMERICO
 +
);
  
−
<u>Nota:</u> Ao principio ides ter problema de tipo 'non amosa ningún dato'. O primeiro que tedes que descartar é que a consulta SQL estea ben e devolva datos, polo que vos aconsello que fagades un 'echo' de dita consulta e a executedes no MysqlWorkBench para ver que realmente devolve algún resultado.
+
$stmt->execute([
 +
'valor' => 5
 +
]); </syntaxhighlight>
  
 +
* O nome do parámetro debe coincidir co indicado na sentenza SQL.
  
 +
: Se a consulta emprega:
  
−
<br />
+
::<syntaxhighlight lang="sql">
−
=====Orientado a obxectos=====
+
:valor </syntaxhighlight>
−
* Crearemos unha nova páxina de nome '''consultar_exemplares_por_libro.php'''.
 
  
−
: Modificamos o código para que aparezan os libros dentro da etiqueta <select> de html:
+
: debemos empregar:
−
: Neste caso, na combo imos gardar o código do libro xa que cando escollamos un, necesitamos obter dito código para facer outra consulta buscando os exemplares dese libro...
 
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
::<syntaxhighlight lang="php">
−
<?php
+
$stmt->execute([
−
/* Lembrar quitar isto ao probar o scripts sen erros */
+
'valor' => 5
−
ini_set('display_errors', 1);
+
]); </syntaxhighlight>
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
  
−
require ('/var/www/confBD/bd.php');
+
==== Indicar explicitamente o tipo de dato ====
  
−
try {
+
* Cando enviamos os valores directamente mediante o array de `execute()`, PDO trátaos por defecto como `PDO::PARAM_STR`.
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);  // Indico o nome da bd
 
−
    $conex->set_charset('utf8mb4');
 
  
 +
: Na maioría das consultas MySQL realizará as conversións necesarias segundo o contexto.
  
−
?>
+
* Se queremos indicar explicitamente o tipo empregaremos `bindValue()`:
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
::<syntaxhighlight lang="php">
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
+
$stmt = $conex->prepare(
−
    <head>
+
CONSULTA_CON_PARAM_NUMERICO
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+
);
−
        <title>Operacións BD</title>
 
−
    </head>
 
  
−
    <body>
+
$stmt->bindValue(
−
    <?php       
+
':valor',
−
       
+
5,
−
        $result=$conex->query(CONSULTAR_LIBROS);
+
PDO::PARAM_INT
−
        if ($result && $result->num_rows>0){
+
);
−
            echo "<select name='lstLibros'>";
 
−
            echo "<option value='-1'></option>";
 
−
            while($row = $result->fetch_assoc()) {
 
−
                printf("<option value='%d'>%s</option>",htmlentities($row["id"]),htmlentities($row["titulo"]));
 
−
            }
 
−
            echo "</select>";
 
−
            $result->free(); // Liberamos da memoria os recursos
 
−
        }
 
−
        else {
 
−
            echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
       
 
−
    ?>       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
$stmt->execute(); </syntaxhighlight>
−
}  // Pecha o try da parte superior da páxina
 
−
catch (mysqli_sql_exception $e) {
 
−
    die("Erro mysql: " . $e->getMessage());
 
−
}
 
−
catch (Exception $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
−
:* Liña 32: Engadimos unha entrada en branco un valor -1 no seu value, para 'obrigar' a escoller un valor e que por defecto non apareza cun libro seleccionado.
+
* Algúns dos tipos dispoñibles son:
−
: Dependerá da aplicación, podedes ter que implementar variantes como ter unha entrada que indique 'TODAS' ou facer que por defecto estea seleccionada algunha das entradas...
 
  
−
: Resultado:
+
::<syntaxhighlight lang="text">
−
[[Imagen:Php_BD_9.jpg|300px|center]]
+
PDO::PARAM_INT
 +
PDO::PARAM_STR
 +
PDO::PARAM_BOOL
 +
PDO::PARAM_NULL </syntaxhighlight>
  
 +
* É especialmente recomendable indicar explicitamente o tipo cando este teña importancia para a consulta, por exemplo con `LIMIT` ou `OFFSET`.
  
 +
<br />
  
 +
==== Caso práctico: Enviando parámetros de tipo numérico ====
  
−
* '''Arquivo bd.php'''
+
* Imos facer que o usuario poida seleccionar un libro nunha lista `<select>` e que se amosen os exemplares correspondentes.
−
: Definimos nunha constate a consulta que vai obter os exemplares do libro seleccionado:
+
: Terás que programar que o libro se manteña seleccionado cando cambiemos de libro na lista. Para iso recorda que tes o libro selecccionado e cando creas a lista en html terás que comprobar si e o libro seleccionado, dentro do bucle que xera as etiquetas <option>, coincide coa etiqueta option seleccionada e se é o caso, poñela 'selected'.
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id from exemplares where libro_id=%d order by estado_id');
 
−
</syntaxhighlight>
 
−
:: Como vemos imos enviar o código por medio dun parámetro de tipo numérico (usaremos a función sprintf).
 
−
: Definimos nunha constante onde se atopan as imaxes relacionados cos libros:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CARTAFOL_IMAXES_WEB','http://www.meusitio.es/IMAXES/');  // Debedes cambiar www.meusitioweb.es pola vosa URL ou se non tedes configurado un DNS ou arquivo hosts poñede http::/localhost/IMAXES/
 
−
</syntaxhighlight>
 
−
:: Teredes que cambiar a url pola vosa.
 
  
 +
[[Imagen:Php_BD_9.jpg|800px|center]]
  
  
−
* Para facer que cando se seleccione un elemento do lista se 'active' o formulario, existen múltiples formas, unha delas pode ser:
 
−
:::            echo "<select name='lstLibros' <b>onchange='this.form.submit()'</b>>";
 
−
::: Será necesario 'envolver' a etiqueta <select> cun <form>, con method='post' e action apuntando á mesma páxina.
 
  
−
:* O código que vai amosar os exemplares do libro seleccionado será o seguinte:
 
  
−
:::<syntaxhighlight lang="php" enclose="div" highlight="42,44,47-56" >
+
* Primeiro definimos as consultas.
−
<?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');
+
'''Arquivo: bd.php'''
  
−
try {
+
::<syntaxhighlight lang="php">
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);  // Indico o nome da bd
+
define(
−
    $conex->set_charset('utf8mb4');
+
'CONSULTAR_LIBROS',
−
?>
+
'SELECT id, titulo
 +
FROM libros
 +
ORDER BY titulo'
 +
);
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
define(
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
+
'CONSULTAR_EXEMPLARES_POR_LIBRO',
−
    <head>
+
'SELECT libro_id,
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+
id_exemplar,
−
        <title>Operacións BD</title>
+
imaxe,
−
    </head>
+
prezo,
−
 
+
estado_id
−
    <body>
+
FROM exemplares
−
    <?php       
+
WHERE libro_id = :libro_id
−
       
+
ORDER BY estado_id'
−
        $result=$conex->query(CONSULTAR_LIBROS);
+
);
−
        if ($result && $result->num_rows>0){
+
 
−
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
+
define(
−
            echo "<select name='lstLibros' onChange='this.form.submit();'>";
+
'CARTAFOL_IMAXES_WEB',
−
            echo "<option value='-1'></option>";
+
'http://localhost/IMAXES/'
−
            while($row = $result->fetch_assoc()) {
+
); </syntaxhighlight>
−
                printf("<option value='%d'>%s</option>",htmlentities($row["id"]),htmlentities($row["titulo"]));
+
 
−
            }
+
===== Solución empregando PDO =====
−
            echo "</select>";
+
: Fixarse que
−
            printf("</form>");
+
 
−
            $result->free(); // Liberamos da memoria os recursos
+
 
−
        }
+
'''Arquivo: consultar_exemplares_por_libro.php'''
−
        else {
 
−
            echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
    ?>      
 
−
    <?php       
 
−
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
 
−
        if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
 
−
            echo "<div style='margin-top:40px'>";
 
−
            $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
 
−
            $result=$conex->query($query);
 
−
            if ($result && ($result->num_rows>0)){
 
−
                echo "<table border='1'>";
 
−
                echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
 
−
                while($row = $result->fetch_assoc()) {
 
−
                    echo '<tr>';
 
−
                    printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%d</td>",
 
−
                            htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),
 
−
                            CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),htmlentities($row["prezo"]),htmlentities($row["estado_id"]));
 
−
                    echo '</tr>';
 
−
                }
 
−
                echo '</table>';
 
−
                $result->free();  // Liberamos da memoria os recursos
 
−
            }
 
−
            else {
 
−
                echo "Non hai datos que amosar!!!!";
 
−
            }
 
−
            echo "</div>";
 
−
        }
 
−
        // $conex->close();  // Non fai falla pechar a conexión xa que esta se pecha ao final do páxina web. Noutras páxinas pode ser necesario
 
−
       
 
−
    ?>       
 
  
−
    </body>   
+
::<syntaxhighlight lang="php">
−
</html>
 
  
 
<?php
 
<?php
−
}  // Pecha o try da parte superior da páxina
 
−
catch (mysqli_sql_exception $e) {
 
−
    die("Erro mysql: " . $e->getMessage());
 
−
}
 
−
catch (Exception $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
−
?> 
 
−
</syntaxhighlight>
 
  
−
::* Liña 42: Comprobamos que veñan datos no formulario (que se escollera algún exemplar da lista) e que sexa de tipo numérico. Tamén comprobamos se seleccionamos o elemento baleiro (valor -1 na combo).
+
ini_set('display_errors', '1');
−
::* Liña 44: Definimos a consulta a realizar contra a base de datos, utilizando a constante definida e substituíndo o parámetro polo código do libro seleccionado.
+
error_reporting(E_ALL);
−
::* Liñas 47-52: Construímos a táboa para que amose os datos. Fixarse que na imaxe temos que definir o atributo 'alt' para que cumpra o estándar XHTML, polo que enviamos dúas veces o nome do arquivo. No dato que vai no atributo 'src' enviamos o nome do arquivo xunto coa URL onde se atopan as imaxes.
 
  
−
::* Liña 62: Non pechamos a conexión xa que esta se pecha ao final de todo. Sempre que se abra unha conexión temos que ter o código que a pecha. Pode suceder que na mesma páxina abramos e pechemos varias veces a conexión ao introducir código con include.
+
require '/var/www/confBD/bd.php';
  
 +
$libros = [];
 +
$exemplares = [];
  
 +
$libroSeleccionado = filter_input(
 +
    INPUT_POST,
 +
    'lstLibros',
 +
    FILTER_VALIDATE_INT
 +
);
  
 +
try {
  
−
<br />
+
    $dsn = sprintf(
−
* Intenta agora modificar o código anterior e fai que o select manteña seleccionado o libro cando aparecen os exemplares.
+
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
 +
        SERVIDOR,
 +
        BD
 +
    );
  
 +
    $conex = new PDO(
 +
        $dsn,
 +
        USUARIO,
 +
        PASSWORD,
 +
        [
 +
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
 +
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
 +
        ]
 +
    );
  
 +
    /*
 +
    * Obtemos os libros que se van amosar
 +
    * no elemento select.
 +
    */
 +
    $stmt = $conex->query(CONSULTAR_LIBROS);
  
 +
    $libros = $stmt->fetchAll();
  
  
−
<br />
+
    /*
−
: Posible solución:
+
    * Se o usuario seleccionou un libro válido,
−
:::<syntaxhighlight lang="php" enclose="div" highlight="7-10" >
+
    * consultamos os seus exemplares.
−
        $result=$conex->query(CONSULTAR_LIBROS);
+
    */
−
        if ($result && $result->num_rows>0){
+
    if (
−
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
+
        $libroSeleccionado !== false &&
−
            echo "<select name='lstLibros' onChange='this.form.submit();'>";
+
        $libroSeleccionado !== null &&
−
            echo "<option value='-1'></option>";
+
        $libroSeleccionado !== -1
−
            while($row = $result->fetch_assoc()) {
+
    ) {
−
                $cadea = '';
 
−
                if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){ // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
 
−
                    $cadea="selected='selected'";
 
−
                }
 
  
−
                printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
+
        $stmt = $conex->prepare(
−
            }
+
             CONSULTAR_EXEMPLARES_POR_LIBRO
−
            echo "</select>";
+
         );
−
            printf("</form>");
 
−
             $result->free();  // Liberamos da memoria os recursos
 
−
         }
 
−
        else {
 
−
            echo "Non hai datos que amosar!!!!";
 
−
        }
 
  
−
</syntaxhighlight>
+
        $stmt->bindValue(
 +
            ':libro_id',
 +
            $libroSeleccionado,
 +
            PDO::PARAM_INT
 +
        );
  
 +
        $stmt->execute();
  
 +
        $exemplares = $stmt->fetchAll();
 +
    }
  
 +
} catch (PDOException $e) {
  
 +
    error_log($e->getMessage());
 +
    die('Erro relacionado coa base de datos.');
  
 +
} catch (Throwable $e) {
  
−
<br />
+
    die('Produciuse un erro na aplicación.');
−
* O código completo sería o seguinte:
 
  
−
'''Arquivo: bd.php''':
+
}
  
−
:::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
?>
−
<?php
 
  
−
define ('SERVIDOR','localhost');
+
<!DOCTYPE html>
−
define ('USUARIO','user_php');
 
−
define ('PASSWORD','user_php');
 
−
define ('BD','php');
 
  
 +
<html lang="gl">
 +
<head>
 +
    <meta charset="UTF-8">
 +
    <title>Exemplares por libro</title>
 +
</head>
  
−
define ('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
+
<body>
−
define ('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
 
−
define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id'
 
−
            . ' from exemplares '  // Necesita un espazo en branco antes do from para concatenar có anterior
 
−
            . ' where libro_id=%d order by titulo');  //  Necesita un espazo en branco antes do where para concatenar có anterior
 
−
define ('CARTAFOL_IMAXES_WEB','http://localhost/IMAXES/');
 
  
−
</syntaxhighlight>
+
<form method="post">
  
 +
<select
 +
    name="lstLibros"
 +
    onchange="this.form.submit()"
 +
>
  
−
'''Arquivo: consultar_exemplares_por_libro.php''':
+
    <option value="-1">
 +
        -- Escolle un libro --
 +
    </option>
  
−
:::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
    <?php foreach ($libros as $libro): ?>
−
<?php
+
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
+
        <option
−
ini_set('display_errors', 1);
+
            value="<?= (int) $libro['id'] ?>"
−
ini_set('display_startup_errors', 1);
+
            <?php
−
error_reporting(E_ALL);
+
            if (
 +
                $libroSeleccionado !== false &&
 +
                $libroSeleccionado !== null &&
 +
                (int) $libro['id'] === $libroSeleccionado
 +
            ) {
 +
                echo 'selected';
 +
            }
 +
            ?>
 +
        >
 +
            <?= htmlspecialchars(
 +
                $libro['titulo'],
 +
                ENT_QUOTES | ENT_SUBSTITUTE,
 +
                'UTF-8'
 +
            ) ?>
 +
        </option>
 +
 
 +
    <?php endforeach; ?>
 +
 
 +
</select>
 +
 
 +
</form>
 +
 
 +
<?php if (
 +
    $libroSeleccionado !== false &&
 +
    $libroSeleccionado !== null &&
 +
    $libroSeleccionado !== -1
 +
): ?>
  
−
require ('/var/www/confBD/bd.php');
+
<?php if ($exemplares): ?>
  
−
try {
+
     <table border="1">
−
     $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);  // Indico o nome da bd
 
−
    $conex->set_charset('utf8mb4');
 
  
−
?>
+
        <tr>
 +
            <th>Num. exemplar</th>
 +
            <th>Imaxe</th>
 +
            <th>Prezo</th>
 +
            <th>Estado</th>
 +
        </tr>
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
        <?php foreach ($exemplares as $exemplar): ?>
−
<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>Operacións BD</title>
 
−
    </head>
 
  
−
    <body>
+
             <tr>
−
    <?php       
 
−
       
 
−
        $result=$conex->query(CONSULTAR_LIBROS);
 
−
        if ($result && $result->num_rows>0){
 
−
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
 
−
            echo "<select name='lstLibros' onChange='this.form.submit();'>";
 
−
             echo "<option value='-1'></option>";
 
−
            while($row = $result->fetch_assoc()) {
 
−
                $cadea = '';
 
−
                if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){  // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
 
−
                    $cadea="selected='selected'";
 
−
                }
 
  
−
                 printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
+
                 <td>
−
            }
+
                    <?= (int) $exemplar['id_exemplar'] ?>
−
            echo "</select>";
+
                </td>
−
            printf("</form>");
+
 
−
            $result->free();  // Liberamos da memoria os recursos
+
                <td>
−
        }
+
                    <img
−
        else {
+
                        src="<?= htmlspecialchars(
−
            echo "Non hai datos que amosar!!!!";
+
                            CARTAFOL_IMAXES_WEB .
−
        }
+
                            ($exemplar['imaxe'] ?? ''),
−
    ?>       
+
                            ENT_QUOTES | ENT_SUBSTITUTE,
−
    <?php       
+
                            'UTF-8'
−
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
+
                        ) ?>"
−
        if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
+
                        alt="<?= htmlspecialchars(
−
            echo "<div style='margin-top:40px'>";
+
                            $exemplar['imaxe'] ?? '',
−
            $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
+
                            ENT_QUOTES | ENT_SUBSTITUTE,
−
            $result=$conex->query($query);
+
                            'UTF-8'
−
            if ($result && ($result->num_rows>0)){
+
                        ) ?>"
−
                echo "<table border='1'>";
+
                        width="42"
−
                echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
+
                        height="42"
−
                while($row = $result->fetch_assoc()) {
+
                     >
−
                     echo '<tr>';
+
                </td>
−
                    printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%d</td>",
+
 
−
                            htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),
+
                <td>
−
                            CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),htmlentities($row["prezo"]),htmlentities($row["estado_id"]));
+
                    <?= number_format(
−
                    echo '</tr>';
+
                        (float) $exemplar['prezo'],
−
                }
+
                        2,
−
                echo '</table>';
+
                        ',',
−
                $result->free();  // Liberamos da memoria os recursos
+
                        '.'
−
            }
+
                    ) ?>
−
            else {
+
                 </td>
−
                 echo "Non hai datos que amosar!!!!";
 
−
            }
 
−
            echo "</div>";
 
−
        }
 
−
        // $conex->close();  // Non fai falla pechar a conexión xa que esta se pecha ao final do páxina web. Noutras páxinas pode ser necesario
 
−
       
 
−
    ?>      
 
  
−
    </body>  
+
                <td>
−
</html>
+
                    <?= (int) $exemplar['estado_id'] ?>
 +
                </td>
  
−
<?php
+
            </tr>
−
}  // Pecha o try da parte superior da páxina
 
−
catch (mysqli_sql_exception $e) {
 
−
    die("Erro mysql: " . $e->getMessage());
 
−
}
 
−
catch (Exception $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
 +
        <?php endforeach; ?>
  
 +
    </table>
  
 +
<?php else: ?>
  
−
<br />
+
    <p>Non hai exemplares que amosar.</p>
−
* '''<u>Exercicio proposto:</u>''' Cando aparecen os exemplares do libro seleccionado, fai que apareza enriba da táboa, dentro dun &lt;h3&mt; o nome do libro seleccionado.
 
  
 +
<?php endif; ?>
  
 +
<?php endif; ?>
  
 +
</body>
 +
</html>
 +
</syntaxhighlight>
  
 +
* Neste exemplo empregamos `filter_input()` con `FILTER_VALIDATE_INT` para comprobar que o dato recibido é un número enteiro válido.
  
 +
* Despois utilizamos unha sentenza preparada e:
  
−
<br />
+
::<syntaxhighlight lang="php">
−
* Imos realizar <u>unha mellora</u> nos datos que se visualizan.
+
PDO::PARAM_INT </syntaxhighlight>
−
: Como vemos, aparece o código do estado do libro. O que queremos amosar é a descrición, polo que teremos que facer un Join coa táboa ESTADOS.
 
  
−
'''Arquivo bd.inc.php''':
+
: para indicar explicitamente que o identificador é numérico.
−
:::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id,estados.descripcion as desc_estado'
 
−
            . ' from exemplares INNER JOIN estados ON (estados.id=exemplares.estado_id)'  // Atención aos espazos en branco no from. Se non os pos quedaría mal a sentenza. Lembra facer un echo da sentenza para ver que todo estea ben
 
−
            . ' where libro_id=%d order by desc_estado');  //  Necesita un espazo en branco antes do where para concatenar có anterior
 
−
</syntaxhighlight>
 
  
 +
* Non necesitamos empregar `mysqli_real_escape_string()` nin concatenar o dato dentro da consulta.
  
 +
==== Amosar a descrición do estado ====
  
−
<br />
+
* Podemos mellorar a consulta realizando un `JOIN` coa táboa `estados` para amosar o estado do exemplar.
−
'''Arquivo: consultar_exemplares_por_libro.php''':
 
−
:::<syntaxhighlight lang="php" enclose="div" highlight="14" >
 
−
        ..................
 
  
−
    <?php       
+
[[Imagen:Php_BD_12.jpg|800px|center]]
−
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
 
−
        if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
 
−
            echo "<div style='margin-bottom:40px'>";
 
−
            $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
 
−
            $result=$conex->query($query);
 
−
            if ($result && ($result->num_rows>0)){
 
−
                echo "<table border='1'>";
 
−
                echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
 
−
                while($row = $result->fetch_assoc()) {
 
−
                    echo '<tr>';
 
−
                    printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%s</td>",
 
−
                            htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),
 
−
                            htmlentities($row["prezo"]),htmlentities($row["desc_estado"]));
 
−
                    echo '</tr>';
 
−
                }
 
−
                echo '</table>';
 
−
                $result->free();  // Liberamos da memoria os recursos
 
−
            }
 
−
            else {
 
−
                echo "Non hai datos que amosar!!!!";
 
−
            }
 
−
            echo "</div>";
 
−
        }
 
−
       
 
−
    ?>   
 
  
−
        ..................
 
  
−
</syntaxhighlight>
+
'''Arquivo: bd.php'''
  
−
* Liña 14: Modificamos o tipo de parámetro que pasa de ser numérico (%d) a cadea (%s) e amosamos a descrición do estado (as dúas columnas do select non deben ter o mesmo nome, descrición do libro e descrición do estado).
+
::<syntaxhighlight lang="php">
 +
define(
 +
'CONSULTAR_EXEMPLARES_POR_LIBRO',
 +
'SELECT exemplares.libro_id,
 +
exemplares.id_exemplar,
 +
exemplares.imaxe,
 +
exemplares.prezo,
 +
estados.descripcion AS desc_estado
 +
FROM exemplares
 +
INNER JOIN estados
 +
ON estados.id = exemplares.estado_id
 +
WHERE exemplares.libro_id = :libro_id
 +
ORDER BY desc_estado'
 +
); </syntaxhighlight>
  
 +
* Para amosar o estado:
  
 +
::<syntaxhighlight lang="php">
 +
echo htmlspecialchars(
 +
$exemplar['desc_estado'],
 +
ENT_QUOTES | ENT_SUBSTITUTE,
 +
'UTF-8'
 +
); </syntaxhighlight>
  
−
* Resultado:
+
<br />
  
−
[[Imagen:Php_BD_12.jpg|400px|center]]
+
==== Exercicio proposto ====
  
 +
* Crea unha páxina chamada `consultar_exemplares_por_estado.php`.
  
 +
* Debe:
  
 +
:* Amosar os estados da táboa `ESTADOS` mediante botóns de opción (`radio`).
 +
:* Ao seleccionar un estado, realizarase automaticamente o envío do formulario.
 +
:* O estado seleccionado debe manterse marcado.
 +
:* Amosaranse os exemplares correspondentes ao estado seleccionado.
 +
:* A consulta deberá realizarse mediante PDO e unha sentenza preparada.
 +
:* O identificador do estado deberá validarse como número enteiro.
 +
:* Empregarase `PDO::PARAM_INT` para enviar o parámetro.
  
−
<br />
 
−
=====Empregando PDO=====
 
−
* Crearemos unha nova páxina de nome '''consultar_exemplares_por_libro.php'''.
 
  
−
: Modificamos o código para que aparezan os libros dentro da etiqueta <select> de html:
 
−
: Neste caso, na combo imos gardar o código do libro xa que cando escollamos un, necesitamos obter dito código para facer outra consulta buscando os exemplares dese libro...
 
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
[[Imagen:Php_BD_13.jpg|800px|center]]
−
<?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');
+
:<u>Nota:</u> Podedes darlle un formato máis 'bonito' con CSS.
  
−
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);
 
  
−
?>
+
* Consultas propostas:
  
 +
::<syntaxhighlight lang="php">
 +
define(
 +
'CONSULTAR_ESTADOS',
 +
'SELECT id, descripcion
 +
FROM estados
 +
ORDER BY descripcion'
 +
);
  
−
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
define(
−
    <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
+
'CONSULTAR_EXEMPLARES_POR_ESTADO',
 +
'SELECT libros.titulo,
 +
exemplares.id_exemplar,
 +
exemplares.prezo
 +
FROM exemplares
 +
INNER JOIN libros
 +
ON libros.id = exemplares.libro_id
 +
WHERE exemplares.estado_id = :estado_id
 +
ORDER BY libros.titulo'
 +
); </syntaxhighlight>
  
−
    <head>
+
<br />
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Operacións BD</title>
 
−
    </head>
 
  
−
    <body>
+
=== Caso práctico: Enviando parámetros de tipo cadea ===
−
        <?php
 
  
−
        $result = $conex->query(CONSULTAR_LIBROS);
+
* Imos crear unha páxina que permita '''buscar libros polo nome dun autor'''.
−
        if ($result && $result->rowCount() > 0) {
+
: O que se pretende programar é algo parecido a isto:
−
            echo "<select name='lstLibros'>";
+
[[Imagen:Php_BD_11.jpg|800px|center]]
−
            echo "<option value='-1'></option>";
 
−
            while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
 
−
                printf("<option value='%d'>%s</option>", htmlentities($row["id"]), htmlentities($row["titulo"]));
 
−
            }
 
−
            echo "</select>";
 
  
−
            $result = null;
 
−
        } else {
 
−
            echo "Non hai datos que amosar!!!!";
 
−
        }
 
  
  
−
        ?>
+
: O usuario introducirá nunha caixa de texto o nome, os apelidos ou unha parte deles.
−
    </body>
 
  
−
    </html>
+
: Por exemplo, se introduce:
  
−
<?php
+
::<syntaxhighlight lang="text">
−
}    // Pecha o try do principio da páxina
+
gar </syntaxhighlight>
−
catch (PDOException $e) {
 
−
    die("Erro PDO: " . $e->getMessage());
 
−
} catch (Exception $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
} finally {
 
−
    $conex = null; // Pechar a conexión
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
−
:* Liña 32: Engadimos unha entrada en branco un valor -1 no seu value, para 'obrigar' a escoller un valor e que por defecto non apareza cun libro seleccionado.
+
: poderían atoparse autores como `García`, `Vargas`, etc., dependendo dos datos existentes na base de datos.
−
: Dependerá da aplicación, podedes ter que implementar variantes como ter unha entrada que indique 'TODAS' ou facer que por defecto estea seleccionada algunha das entradas...
 
  
−
: Resultado:
+
* Para realizar a busca empregaremos:
−
[[Imagen:Php_BD_9.jpg|300px|center]]
 
  
 +
:* Un formulario HTML con método `POST`.
 +
:* Unha consulta SQL co operador `LIKE`.
 +
:* Unha '''sentenza preparada PDO''' para enviar o texto introducido polo usuario.
 +
:* O parámetro `PDO::PARAM_STR`, xa que o dato enviado é unha cadea.
 +
:* `fetchAll()` para recuperar os resultados.
 +
:* `htmlspecialchars()` cando amosemos datos dentro do HTML.
  
 +
* A consulta devolverá:
  
 +
:* O nome completo do autor.
 +
:* O identificador do libro.
 +
:* O título do libro.
  
−
* '''Arquivo bd.php'''
+
* Como un mesmo autor pode ter varios libros, evitaremos repetir o seu nome en todas as filas da táboa.
−
: Definimos nunha constate a consulta que vai obter os exemplares do libro seleccionado:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id from exemplares where libro_id= :libro_id order by estado_id');
 
−
</syntaxhighlight>
 
−
:: Como vemos imos enviar o código por medio dun parámetro de tipo numérico.
 
−
: Definimos nunha constante onde se atopan as imaxes relacionados cos libros:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CARTAFOL_IMAXES_WEB','http://www.meusitio.es/IMAXES/');  // Debedes cambiar www.meusitioweb.es pola vosa URL ou se non tedes configurado un DNS ou arquivo hosts poñede http::/localhost/IMAXES/
 
−
</syntaxhighlight>
 
−
:: Teredes que cambiar a url pola vosa.
 
  
 +
* Ademais, o título de cada libro será un enlace que permitirá abrir unha nova xanela coa páxina:
  
 +
::<syntaxhighlight lang="text">
 +
detalle_libro.php </syntaxhighlight>
  
−
* Para facer que cando se seleccione un elemento do lista se 'active' o formulario, existen múltiples formas, unha delas pode ser:
+
: á que enviaremos o identificador do libro mediante `GET`.
−
:::            echo "<select name='lstLibros' <b>onchange='this.form.submit()'</b>>";
 
−
::: Será necesario 'envolver' a etiqueta <select> cun <form>, con method='post' e action apuntando á mesma páxina.
 
  
−
:* O código que vai amosar os exemplares do libro seleccionado será o seguinte:
+
: Para abrir esa páxina nunha nova xanela empregaremos JavaScript e a función `window.open()`.
  
−
:::<syntaxhighlight lang="php" enclose="div" highlight="46,54-63,49-50" >
+
: Polo tanto, o funcionamento xeral será:
−
<?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');
+
::<syntaxhighlight lang="text">
 +
Usuario introduce o nome do autor
 +
↓
 +
Validamos o texto
 +
↓
 +
prepare() + bindValue()
 +
↓
 +
Consulta con LIKE
 +
↓
 +
Recuperamos os libros
 +
↓
 +
Amosamos autor e título
 +
↓
 +
Preme sobre un título
 +
↓
 +
window.open()
 +
↓
 +
detalle_libro.php?libro=ID </syntaxhighlight>
  
−
try {
+
===== Arquivo bd.php =====
−
    $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);
 
  
−
?>
+
* Engadimos a consulta que permite buscar libros polo nome ou apelidos dun autor:
  
 +
::<syntaxhighlight lang="php">
 +
define(
 +
'CONSULTAR_LIBROS_POR_AUTOR',
 +
"SELECT
 +
CONCAT(
 +
autores.apelidos,
 +
', ',
 +
autores.nome
 +
) AS nome_autor,
 +
libros.id AS id_libro,
 +
libros.titulo
 +
FROM libros
 +
INNER JOIN libro_autor
 +
ON libro_autor.libro_id = libros.id
 +
INNER JOIN autores
 +
ON libro_autor.autor_id = autores.id
 +
WHERE CONCAT(
 +
autores.nome,
 +
' ',
 +
autores.apelidos
 +
) LIKE :autor
 +
ORDER BY nome_autor, libros.titulo"
 +
); </syntaxhighlight>
  
−
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
* A consulta une as táboas:
−
    <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
 
  
−
    <head>
+
::<syntaxhighlight lang="text">
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+
libros
−
        <title>Operacións BD</title>
+
libro_autor
−
    </head>
+
autores </syntaxhighlight>
  
−
    <body>
+
: para obter os libros correspondentes a cada autor.
−
        <?php
+
 
 +
* Mediante:
  
−
        $result = $conex->query(CONSULTAR_LIBROS);
+
::<syntaxhighlight lang="sql">
−
        if ($result && $result->rowCount() > 0) {
+
CONCAT(
−
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
+
autores.nome,
−
            echo "<select name='lstLibros' onChange='this.form.submit();'>";
+
' ',
−
            echo "<option value='-1'></option>";
+
autores.apelidos
−
            while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+
) </syntaxhighlight>
−
                printf("<option value='%d'>%s</option>", htmlentities($row["id"]), htmlentities($row["titulo"]));
 
−
            }
 
−
            echo "</select>";
 
−
            printf("</form>");
 
−
            $result = null;
 
−
        } else {
 
−
            echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
        ?>
 
  
−
        <?php       
+
: construímos unha cadea formada polo nome e os apelidos do autor.
−
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
 
−
        if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
 
−
            echo "<div style='margin-top:40px'>";
 
  
−
            $stmt=$conex->prepare(CONSULTAR_EXEMPLARES_POR_LIBRO);    // Temos que poñer prepare
+
* Sobre esa cadea aplicamos:
−
            $result = $stmt->execute([':libro_id' => $_POST['lstLibros']]);
 
  
−
            if ($result) {
+
::<syntaxhighlight lang="sql">
−
                while ($fila = $stmt->fetch(PDO::FETCH_OBJ)) {
+
LIKE :autor </syntaxhighlight>
−
                    echo "<table border='1'>";
 
−
                    echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
 
−
                    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
 
−
                        echo '<tr>';
 
−
                        printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%d</td>",
 
−
                                htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"] ?? ''),
 
−
                                CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"] ?? ''),htmlentities($row["prezo"] ?? 0),htmlentities($row["estado_id"]));
 
−
                        echo '</tr>';
 
−
                    }
 
−
                    echo '</table>';
 
−
   
 
−
                }
 
−
                $stmt = null;  // Liberamos da memoria os recursos
 
−
            }
 
−
            else {
 
−
                echo "Non hai datos que amosar!!!!";
 
−
            }
 
−
            echo "</div>";
 
−
        }
 
−
       
 
−
    ?>  
 
  
−
    </body>
+
: sendo `:autor` o parámetro que enviaremos posteriormente desde PDO.
  
−
    </html>
+
* O parámetro debe representar o valor completo.
  
−
<?php
+
: Polo tanto, non escribiremos:
−
}    // 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>
 
  
−
::* Liña 46: Comprobamos que veñan datos no formulario (que se escollera algún exemplar da lista) e que sexa de tipo numérico. Tamén comprobamos se seleccionamos o elemento baleiro (valor -1 na combo).
+
::<syntaxhighlight lang="sql">
−
::* Liña 49-50: Definimos a consulta a realizar contra a base de datos, utilizando a constante definida e substituíndo o parámetro polo código do libro seleccionado.
+
LIKE '%:autor%' </syntaxhighlight>
−
::* Liñas 54-63: Construímos a táboa para que amose os datos. Fixarse que na imaxe temos que definir o atributo 'alt' para que cumpra o estándar XHTML, polo que enviamos dúas veces o nome do arquivo. No dato que vai no atributo 'src' enviamos o nome do arquivo xunto coa URL onde se atopan as imaxes.
 
  
−
::* Liña 49: Non pechamos a conexión xa que esta se pecha ao final de todo. Sempre que se abra unha conexión temos que ter o código que a pecha. Pode suceder que na mesma páxina abramos e pechemos varias veces a conexión ao introducir código con include.
+
: senón que os caracteres `%` serán engadidos ao valor desde PHP:
  
−
:: Liña 60-61: Empregamos o operador ?? que ven indicar que se o valor é nulo dalle o valor que ten ao carón da ?. Como imaxe e prezo poden valor null desta forma aseguramos que htmlentities non dea un warning se o valor é nulo.
+
::<syntaxhighlight lang="php">
 +
'%' . $textoBuscar . '%' </syntaxhighlight>
  
 +
===== Solución completa =====
  
 +
'''Arquivo: buscar_libros_por_autor.php'''
  
 +
::<syntaxhighlight lang="php">
 +
<?php
  
−
<br />
+
/*
−
* Intenta agora modificar o código anterior e fai que o select manteña seleccionado o libro cando aparecen os exemplares.
+
* Estas directivas utilízanse durante o desenvolvemento.
 +
* Nun servidor en produción non se deberían amosar
 +
* os erros PHP ao usuario.
 +
*/
 +
ini_set('display_errors', '1');
 +
error_reporting(E_ALL);
  
 +
require '/var/www/confBD/bd.php';
  
  
 +
/*
 +
* Variables que utilizaremos posteriormente no HTML.
 +
*/
 +
$erro = '';
 +
$libros = [];
  
  
−
<br />
+
/*
−
: Posible solución:
+
* Recuperamos o valor enviado polo formulario.
−
:::<syntaxhighlight lang="php" enclose="div" highlight="7-11" >
+
*
−
        $result = $conex->query(CONSULTAR_LIBROS);
+
* O operador ?? permite indicar unha cadea baleira
−
        if ($result && $result->rowCount() > 0) {
+
* se o formulario aínda non foi enviado.
−
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
+
*
−
            echo "<select name='lstLibros' onChange='this.form.submit();'>";
+
* trim() elimina espazos en branco ao principio
−
            echo "<option value='-1'></option>";
+
* e ao final da cadea.
−
            while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+
*/
−
                $cadea = '';
+
$textoBuscar = trim(
−
                if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){  // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
+
    $_POST['txtAutor'] ?? ''
−
                    $cadea="selected='selected'";
+
);
−
                }
 
−
                printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
 
−
            }
 
−
            echo "</select>";
 
−
            printf("</form>");
 
−
            $result = null;
 
−
        } else {
 
−
            echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
</syntaxhighlight>
 
  
  
 +
try {
  
 +
    /*
 +
    * Creamos o DSN.
 +
    */
 +
    $dsn = sprintf(
 +
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
 +
        SERVIDOR,
 +
        BD
 +
    );
  
  
 +
    /*
 +
    * Abrimos a conexión.
 +
    *
 +
    * Os datos SERVIDOR, USUARIO, PASSWORD e BD
 +
    * están definidos no arquivo bd.php.
 +
    */
 +
    $conex = new PDO(
 +
        $dsn,
 +
        USUARIO,
 +
        PASSWORD,
 +
        [
 +
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
 +
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
 +
        ]
 +
    );
  
−
<br />
 
−
* O código completo sería o seguinte:
 
  
−
'''Arquivo: bd.php''':
+
    /*
 +
    * Comprobamos se o formulario foi enviado.
 +
    */
 +
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  
−
:::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
        /*
−
<?php
+
        * Comprobamos que se introduciu algún texto.
 +
        */
 +
        if ($textoBuscar === '') {
  
−
define ('SERVIDOR','localhost');
+
            $erro = 'Debes introducir un texto para realizar a busca.';
−
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');
+
        * Como regra da aplicación esiximos
−
define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id from exemplares where libro_id= :libro_id order by estado_id');
+
        * polo menos 3 caracteres.
−
define ('CARTAFOL_IMAXES_WEB','http://www.exames.es/IMAXES/');
+
        */
−
</syntaxhighlight>
+
        } elseif (mb_strlen($textoBuscar) < 3) {
  
 +
            $erro =
 +
                'A cadea a buscar debe ter polo menos 3 caracteres.';
 +
        } else {
  
−
'''Arquivo: consultar_exemplares_por_libro.php''':
+
            /*
 +
            * Preparamos a consulta.
 +
            */
 +
            $stmt = $conex->prepare(
 +
                CONSULTAR_LIBROS_POR_AUTOR
 +
            );
  
−
:::<syntaxhighlight lang="php" 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');
+
            /*
 +
            * Engadimos os comodíns % de LIKE ao valor
 +
            * que enviamos ao parámetro.
 +
            *
 +
            * Por exemplo:
 +
            *
 +
            * gar
 +
            *
 +
            * convértese en:
 +
            *
 +
            * %gar%
 +
            */
 +
            $stmt->bindValue(
 +
                ':autor',
 +
                '%' . $textoBuscar . '%',
 +
                PDO::PARAM_STR
 +
            );
  
−
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);
 
  
−
?>
+
            /*
 +
            * Executamos a consulta.
 +
            */
 +
            $stmt->execute();
  
  
−
    <!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">
+
            * Recuperamos todos os resultados.
 +
            */
 +
            $libros = $stmt->fetchAll();
  
−
    <head>
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Operacións BD</title>
 
−
    </head>
 
  
−
    <body>
+
            /*
−
        <?php
+
            * Se non hai filas gardamos unha mensaxe
 +
            * para amosala posteriormente.
 +
            */
 +
            if (!$libros) {
  
−
        $result = $conex->query(CONSULTAR_LIBROS);
+
                 $erro =
−
        if ($result && $result->rowCount() > 0) {
+
                     'Non se atoparon libros para ese autor.';
−
            printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
 
−
            echo "<select name='lstLibros' onChange='this.form.submit();'>";
 
−
            echo "<option value='-1'></option>";
 
−
            while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
 
−
                 $cadea = '';
 
−
                if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){  // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
 
−
                     $cadea="selected='selected'";
 
−
                }
 
−
                printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
 
 
             }
 
             }
−
            echo "</select>";
 
−
            printf("</form>");
 
−
            $result = null;
 
−
        } else {
 
−
            echo "Non hai datos que amosar!!!!";
 
 
         }
 
         }
−
        ?>
+
    }
 +
} catch (PDOException $e) {
  
−
        <?php       
+
    /*
−
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
+
    * Rexistramos a información técnica no log.
−
         if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
+
    * Non debemos amosala ao usuario.
−
            echo "<div style='margin-top:40px'>";
+
    */
 +
    error_log(
 +
         $e->getMessage()
 +
    );
  
−
            $stmt=$conex->prepare(CONSULTAR_EXEMPLARES_POR_LIBRO);   // Temos que poñer prepare
+
    die('Erro relacionado coa base de datos.');
−
            $result = $stmt->execute([':libro_id' => $_POST['lstLibros']]);
+
} catch (Throwable $e) {
  
−
            if ($result) {
+
    die('Produciuse un erro na aplicación.');
−
                while ($fila = $stmt->fetch(PDO::FETCH_OBJ)) {
+
}
−
                    echo "<table border='1'>";
 
−
                    echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
 
−
                    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
 
−
                        echo '<tr>';
 
−
                        printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%d</td>",
 
−
                                htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"] ?? ''),
 
−
                                CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"] ?? ''),htmlentities($row["prezo"] ?? 0),htmlentities($row["estado_id"]));
 
−
                        echo '</tr>';
 
−
                    }
 
−
                    echo '</table>';
 
−
   
 
−
                }
 
−
                $stmt = null; // Liberamos da memoria os recursos
 
−
            }
 
−
            else {
 
−
                echo "Non hai datos que amosar!!!!";
 
−
            }
 
−
            echo "</div>";
 
−
        }
 
−
       
 
−
    ?>
 
  
−
    </body>
+
?>
  
−
    </html>
+
<!DOCTYPE html>
  
−
<?php
+
<html lang="gl">
−
}    // 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>
 
  
 +
    <head>
  
 +
        <meta charset="UTF-8">
 +
        <title>Buscar libros por autor</title>
 +
        <script>
 +
            /*
 +
            * Recibe a URL que queremos abrir
 +
            * e solicita ao navegador unha nova xanela.
 +
            */
 +
            function abrirVentana(url) {
  
 +
                window.open(
 +
                    url,
 +
                    '_blank',
 +
                    'popup,width=600,height=400,noopener'
 +
                );
 +
            }
 +
        </script>
 +
    </head>
  
−
<br />
+
<body>
−
* '''<u>Exercicio proposto:</u>''' Cando aparecen os exemplares do libro seleccionado, fai que apareza enriba da táboa, dentro dun &lt;h3&mt; o nome do libro seleccionado.
 
  
 +
    <h1>Buscar libros por autor</h1>
 +
    <form method="post">
  
 +
        <label for="txtAutor">
 +
            Introduce o nome ou apelidos do autor:
 +
        </label>
 +
        <input
 +
            type="text"
 +
            id="txtAutor"
 +
            name="txtAutor"
 +
            size="50"
 +
            maxlength="100"
 +
            required
 +
            value="<?= htmlspecialchars(
 +
                        $textoBuscar,
 +
                        ENT_QUOTES | ENT_SUBSTITUTE,
 +
                        'UTF-8'
 +
                    ) ?>">
 +
        <button
 +
            type="submit"
 +
            value="BUSCAR">Buscar</button>
 +
        <input
 +
            type="reset"
 +
            value="LIMPAR">
  
 +
    </form>
  
 +
    <?php if ($erro !== ''): ?>
 +
        <p>
  
 +
            <?= htmlspecialchars(
 +
                $erro,
 +
                ENT_QUOTES | ENT_SUBSTITUTE,
 +
                'UTF-8'
 +
            ) ?>
 +
        </p>
 +
    <?php endif; ?>
  
−
<br />
+
    <?php if ($libros): ?>
−
* Imos realizar <u>unha mellora</u> nos datos que se visualizan.
+
        <table border="1">
−
: Como vemos, aparece o código do estado do libro. O que queremos amosar é a descrición, polo que teremos que facer un Join coa táboa ESTADOS.
+
            <tr>
 +
                <th>Autor</th>
 +
                <th>Libro</th>
 +
            </tr>
 +
            <?php
  
−
'''Arquivo bd.php''':
+
        /*
−
:::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
        * Esta variable permite saber cal foi
−
define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id,estados.descripcion as desc_estado'
+
        * o último autor que amosamos.
−
             . ' from exemplares INNER JOIN estados ON (estados.id=exemplares.estado_id)'  // Atención aos espazos en branco no from. Se non os pos quedaría mal a sentenza. Lembra facer un echo da sentenza para ver que todo estea ben
+
        */
−
            . ' where libro_id=:libro_id order by desc_estado');  //  Necesita un espazo en branco antes do where para concatenar có anterior
+
            $ultimoAutor = '';
−
</syntaxhighlight>
+
             foreach ($libros as $libro):
 +
            /*
 +
            * Se o autor desta fila é o mesmo
 +
            * que o da fila anterior,
 +
            * non volvemos amosar o seu nome.
 +
            */
 +
                if (
 +
                    $ultimoAutor ===
 +
                    $libro['nome_autor']
 +
                ) {
  
 +
                    $autorAmosar = '';
 +
                } else {
  
 +
                    $autorAmosar =
 +
                        $libro['nome_autor'];
 +
                }
  
−
<br />
+
            ?>
−
'''Arquivo: consultar_exemplares_por_libro.php''':
+
                <tr>
−
:::<syntaxhighlight lang="php" enclose="div" highlight="17,19" >
+
                    <td>
−
        ..................
+
                        <?= htmlspecialchars(
 +
                            $autorAmosar,
 +
                            ENT_QUOTES | ENT_SUBSTITUTE,
 +
                            'UTF-8'
 +
                        ) ?>
 +
                    </td>
 +
                    <td>
 +
                        <a
 +
                            href="detalle_libro.php?libro=<?= (int) $libro['id_libro'] ?>"
 +
                            onclick="abrirVentana(this.href); return false;">
  
−
    <?php       
+
                            <?= htmlspecialchars(
−
        // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
+
                                $libro['titulo'],
−
        if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
+
                                ENT_QUOTES | ENT_SUBSTITUTE,
−
             echo "<div style='margin-top:40px'>";
+
                                'UTF-8'
 +
                            ) ?>
 +
                        </a>
 +
                    </td>
 +
                </tr>
 +
             <?php
  
−
            $stmt=$conex->prepare(CONSULTAR_EXEMPLARES_POR_LIBRO);    // Temos que poñer prepare
+
            /*
−
            $result = $stmt->execute([':libro_id' => $_POST['lstLibros']]);
+
            * Gardamos o autor actual para comparalo
 +
            * co autor da seguinte fila.
 +
            */
 +
                $ultimoAutor =
 +
                    $libro['nome_autor'];
  
−
             if ($result) {
+
             endforeach;
−
                while ($fila = $stmt->fetch(PDO::FETCH_OBJ)) {
+
            ?>
−
                    echo "<table border='1'>";
+
        </table>
−
                    echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
+
    <?php endif; ?>
−
                    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+
</body>
−
                        echo '<tr>';
+
</html>
−
                        printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%s</td>",
+
</syntaxhighlight>
−
                                htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"] ?? ''),
+
 
−
                                CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"] ?? ''),htmlentities($row["prezo"] ?? 0),htmlentities($row["desc_estado"]));
+
===== Explicación da solución =====
−
                        echo '</tr>';
+
 
−
                    }
+
* Recuperamos o texto enviado polo formulario mediante:
−
                    echo '</table>';
+
 
−
   
+
::<syntaxhighlight lang="php">
−
                }
+
$textoBuscar = trim(
−
                $stmt = null; // Liberamos da memoria os recursos
+
$_POST['txtAutor'] ?? ''
−
            }
+
); </syntaxhighlight>
−
            else {
 
−
                echo "Non hai datos que amosar!!!!";
 
−
            }
 
−
            echo "</div>";
 
−
        }
 
−
    ?>  
 
  
−
        ..................
+
* Non empregamos `htmlspecialchars()` neste momento.
  
−
</syntaxhighlight>
+
: Neste punto estamos '''validando e procesando un dato de entrada'''.
  
−
* Liña 17: Modificamos o tipo de parámetro que pasa de ser numérico (%d) a cadea (%s) e amosamos a descrición do estado (as dúas columnas do select non deben ter o mesmo nome, descrición do libro e descrición do estado).
+
: `htmlspecialchars()` empregarase posteriormente cando o dato se introduza dentro do HTML.
  
  
 +
<br />
 +
'''Validación do dato'''
  
−
* Resultado:
+
* Comprobamos primeiro que a cadea non estea baleira:
  
−
[[Imagen:Php_BD_12.jpg|400px|center]]
+
::<syntaxhighlight lang="php">
 +
if ($textoBuscar === '') { </syntaxhighlight>
  
 +
* Como exemplo dunha regra de validación da aplicación, esiximos tamén unha lonxitude mínima:
  
 +
::<syntaxhighlight lang="php">
 +
elseif (mb_strlen($textoBuscar) < 3) { </syntaxhighlight>
  
 +
: A esixencia de tres caracteres é unha regra da nosa aplicación.
  
 +
: Non é unha medida para evitar inxección SQL.
  
  
  
 
<br />
 
<br />
 +
'''Preparación da consulta'''
 +
 +
* A consulta recibe datos procedentes do usuario, polo que debemos empregar unha sentenza preparada:
  
−
=====Exercicio proposto=====
+
::<syntaxhighlight lang="php">
 +
$stmt = $conex->prepare(
 +
CONSULTAR_LIBROS_POR_AUTOR
 +
); </syntaxhighlight>
  
−
: Crea unha páxina de nome '''consultar_exemplares_por_estado.php''' no que aparezan en forma de radiobutton os estados da táboa ESTADOS.
 
−
: Ao seleccionar un estado, amosaranse en forma de lista ordenada os exemplares (libro_id,num_exemplar,prezo)  ordenados por id (quen queira pode facer un join coa táboa LIBROS e amosar o título en vez do id e ordenar os resultados polo título).
 
−
: O radiobutton debe manterse seleccionado.
 
  
 +
<br />
 +
'''Asociación do parámetro'''
  
 +
* Asociamos o valor ao parámetro `:autor`:
  
 +
::<syntaxhighlight lang="php">
 +
$stmt->bindValue(
 +
':autor',
 +
'%' . $textoBuscar . '%',
 +
PDO::PARAM_STR
 +
); </syntaxhighlight>
  
 +
* Indicamos:
  
 +
::<syntaxhighlight lang="php">
 +
PDO::PARAM_STR </syntaxhighlight>
  
 +
: porque estamos enviando unha cadea.
  
  
  
 
<br />
 
<br />
−
'''POSIBLE SOLUCIÓN ORIENTADA A OBXECTOS''':
+
'''Funcionamento de LIKE'''
  
−
:<u>Nota</u>: Queda para vos facer a solución empregando PDO.
+
* Os caracteres `%` actúan como comodíns.
  
−
'''Arquivo bd.php'''
+
: Se o usuario introduce:
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CONSULTAR_ESTADOS','select id,descripcion from estados order by descripcion');
 
−
define ('CONSULTAR_EXEMPLARES_POR_ESTADO','select titulo,id_exemplar,prezo '
 
−
        . ' from exemplares INNER JOIN libros ON (libros.id = exemplares.libro_id)'
 
−
        .                ' INNER JOIN estados ON (estados.id = exemplares.estado_id)'
 
−
        . ' where estado_id=%d order by titulo');
 
  
−
</syntaxhighlight>  
+
::<syntaxhighlight lang="text">
 +
gar </syntaxhighlight>
  
−
: Definimos dúas consultas. Unha para buscar os estados e outra para buscar os exemplares que teñan un estado determinado (pasado como parámetros)
+
: PHP enviará:
  
 +
::<syntaxhighlight lang="text">
 +
%gar% </syntaxhighlight>
  
−
'''Arquivo consultar_exemplares_por_estado.php'''
+
* Desta forma poderán coincidir cadeas nas que `gar` apareza ao principio, no medio ou ao final.
−
::<syntaxhighlight lang="php" 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 {
+
<br />
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);  // Indico o nome da bd
+
'''Execución e recuperación dos resultados'''
−
    $conex->set_charset('utf8mb4');
 
−
?>
 
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
* Executamos:
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
 
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
+
::<syntaxhighlight lang="php">
−
    <head>
+
$stmt->execute(); </syntaxhighlight>
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+
 
−
        <title>Operacións BD</title>
+
* E recuperamos os resultados:
−
    </head>
 
  
−
    <body>
+
::<syntaxhighlight lang="php">
−
    <body>
+
$libros = $stmt->fetchAll(); </syntaxhighlight>
−
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
 
  
−
        <?php       
+
* Como na conexión configuramos:
−
        $cont=0;    // Conta o número de estados impresos e cando leve múltiplo de dous salta de liña
 
−
        $result=$conex->query(CONSULTAR_ESTADOS);
 
−
        if ($result && $result->num_rows>0){
 
−
            while($row = $result->fetch_assoc()) {
 
−
                $cont++;
 
−
                if(isset($_POST['rbgrpEstado']) && $row['id']==$_POST['rbgrpEstado']){  // Se volve a cargar a páxina e ven cun estado, o seleccionamos
 
−
                    $cadea="checked='checked'";
 
−
                }
 
−
                else{
 
−
                    $cadea='';
 
−
                }
 
−
                printf("<input onclick='javascript:this.form.submit()' type='radio' name='rbgrpEstado' value='%d' %s>%s</input>",
 
−
                        $row["id"],$cadea,$row["descripcion"]);
 
−
                if(($cont%2)==0){  // Cada par cambiamos de liña
 
−
                    echo "<br />";
 
−
                }
 
−
            }
 
−
            $result->free();  // Liberamos da memoria os recursos
 
−
        }
 
−
        else {
 
−
            echo "Non hai estados que amosar!!!!";
 
−
        }
 
−
        ?>       
 
−
        </form>
 
−
       
 
−
       
 
−
        <?php       
 
−
        // CONSULTAMOS OS EXEMPLARES DUN ESTADO SELECCIONADO
 
−
        if (!empty($_POST['rbgrpEstado']) && filter_var($_POST['rbgrpEstado'],FILTER_VALIDATE_INT)){
 
−
           
 
−
            $query=sprintf(CONSULTAR_EXEMPLARES_POR_ESTADO,$conex->real_escape_string($_POST['rbgrpEstado']));
 
−
            $result=$conex->query($query);
 
−
            if ($result && ($result->num_rows>0)){
 
−
                echo "<ol>";
 
−
                while($row = $result->fetch_assoc()) {
 
−
                    echo '<li>';
 
−
                    printf("T&iacute;tulo:%s - Num_exemplar:%d - Prezo:%s",$row["titulo"],$row["id_exemplar"],$row["prezo"]);
 
−
                    echo '</li>';
 
−
                }
 
−
                echo '</ol>';
 
−
                $result->free();  // Liberamos da memoria os recursos
 
−
            }
 
−
            else {
 
−
                echo "Non hai datos que amosar!!!!";
 
−
            }
 
−
           
 
−
        }
 
−
       
 
−
        ?>       
 
−
       
 
−
       
 
−
  </body>   
 
−
</html>
 
  
 +
::<syntaxhighlight lang="php">
 +
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC </syntaxhighlight>
  
−
<?php
+
: cada fila recuperada será un array asociativo.
−
}  // Pecha o try da parte superior da páxina
 
−
catch (mysqli_sql_exception $e) {
 
−
    die("Erro mysql: " . $e->getMessage());
 
−
}
 
−
catch (Exception $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
  
 +
<br />
 +
'''Evitar repetir o nome do autor'''
  
 +
* Pode suceder que un mesmo autor teña varios libros.
  
−
* Resultado:
+
: Por exemplo:
  
−
[[Imagen:Php_BD_13.jpg|400px|center]]
+
::<syntaxhighlight lang="text">
 +
García, Ana | Libro A
 +
García, Ana | Libro B
 +
García, Ana | Libro C
 +
Pérez, Luís | Libro D </syntaxhighlight>
  
 +
* Queremos amosalo desta forma:
  
 +
::<syntaxhighlight lang="text">
 +
García, Ana | Libro A
 +
            | Libro B
 +
            | Libro C
 +
Pérez, Luís | Libro D </syntaxhighlight>
 +
 +
* Para iso gardamos o último autor procesado:
 +
 +
::<syntaxhighlight lang="php">
 +
$ultimoAutor = ''; </syntaxhighlight>
  
 +
* En cada iteración comparamos:
  
−
<br />
+
::<syntaxhighlight lang="php">
 +
if ($ultimoAutor ===$libro['nome_autor']) {
  
−
=== Consulta enviando parámetros de tipo cadea ===
+
  $autorAmosar = '';
  
 +
} else {
 +
    $autorAmosar = $libro['nome_autor'];
 +
}
 +
</syntaxhighlight>
  
 +
* E ao rematar gardamos o autor actual:
  
−
<br />
+
::<syntaxhighlight lang="php">
−
====Orientado a obxectos====
+
$ultimoAutor =
 +
$libro['nome_autor']; </syntaxhighlight>
  
−
* Cando utilicemos datos de tipo cadea, debemos lembrar poñelos entre comillas simples ('dato') na orde SQL.
 
  
 +
<br />
 +
'''Abrir o detalle dun libro'''
  
−
<u>Nota:</u> Lembra que no paso de datos temos que ter en conta os posibles 'ataques' que poidan intentar facerse. Repasar o dado [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Seguridade_no_uso_dos_formularios na wiki anteriormente].
+
* Cada título será un enlace á páxina:
  
 +
::<syntaxhighlight lang="text">detalle_libro.php </syntaxhighlight>
  
−
* Cando facemos unha consulta ou operación contra base de datos (alta, baixa e modificación) imos ter que 'enviar' datos.
+
* O identificador enviarase mediante `GET`:
−
: A forma de facelo é utilizando a consulta SQL cun parámetro e substituíndo dito parámetro polo valor que queiramos enviar.
 
  
−
* Así: SELECT col1, col2 FROM TABOA_1 WHERE col2=''''%d''''
+
::<syntaxhighlight lang="text">detalle_libro.php?libro=5 </syntaxhighlight>
  
−
: No exemplo, col2 é de tipo cadea (por exemplo, varchar ou char).
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define ('CONSULTA_CON_PARAM_CADEA',"SELECT col1, col2 FROM TABOA_1 WHERE col2='%s'");
 
  
−
$query = sprintf(CONSULTA_CON_PARAM_CADEA,'valor_cadea_a_enviar');
 
−
</syntaxhighlight>
 
−
: Fixarse como o parámetro col2 vai entrecomillado e o formato do parámetro é '%s'.
 
  
 +
* O enlace xerado será:
 +
::<syntaxhighlight lang="php">
 +
<a href="detalle_libro.php?libro=<?= (int) $libro['id_libro'] ?>" onclick="abrirVentana(this.href); return false;">
 +
</syntaxhighlight>
  
−
: Agora facemos o mesmo código que no exemplo anterior.
+
* Mediante:
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
......
 
−
$result=$conex->query(CONSULTA_CON_PARAM_CADEA);
 
−
......
 
−
</syntaxhighlight>
 
  
 +
::<syntaxhighlight lang="javascript">this.href </syntaxhighlight>
  
 +
: obtemos a URL real almacenada no atributo `href`.
  
 +
* Chamamos despois á función:
  
−
<br />
+
::<syntaxhighlight lang="javascript">abrirVentana(this.href); </syntaxhighlight>
  
 +
* Finalmente:
  
−
====Empregando PDO====
+
::<syntaxhighlight lang="javascript">return false; </syntaxhighlight>
  
−
* Non hai ningunha diferenza con respecto a enviar un dato de tipo numérico ou do tipo que sexa. Xa se encarga de poñelo entre comillas se é de cadea.
+
: evita que o navegador abra tamén o enlace na xanela actual.
  
  
  
 
<br />
 
<br />
 +
'''Función JavaScript'''
  
−
==== Caso práctico: Enviando parámetros de tipo cadea ====
+
* A función empregada é:
  
−
* Imos a aplicalo ao noso exemplo e imos crear unha páxina no que buscaremos libros en base a un nome do autor.
+
::<syntaxhighlight lang="javascript">
−
:: A busca farase utilizando a [http://www.w3schools.com/sql/sql_like.asp orde SQL '''like'''], que busca patróns de cadea. Este operador non distingue maiúsculas de minúsculas.
+
function abrirVentana(url) {
−
:: Se queremos distinguir maiúsculas de minúsculas utilizaremos o operador [http://stackoverflow.com/questions/14007450/how-do-you-force-mysql-like-to-be-case-sensitive like binary].
 
−
:: O operador like o imos aplicar ao nome completo do autor, polo que teremos que concatenar o nome e apelido. Usaremos a función [http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat-ws concat_ws] ou [http://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_concat concat].
 
  
 +
window.open(
 +
    url,
 +
    '_blank',
 +
    'popup,width=600,height=400,noopener'
 +
);
  
−
* O usuario introducirá nunha caixa de texto o nome (ou parte del) do autor.
+
} </syntaxhighlight>
  
 +
* `window.open()` solicita ao navegador a apertura dunha nova xanela.
  
 +
* Indicamos aproximadamente:
 +
::<syntaxhighlight lang="text">
 +
width=600
 +
height=400 </syntaxhighlight>
  
−
<br />
+
* A opción:
−
=====Orientado a obxectos=====
+
::<syntaxhighlight lang="text">
−
: <u>Nota:</u> Lembra que no paso de datos temos que ter en conta os posibles 'ataques' que poidan intentar facerse. Repasar o dado [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Seguridade_no_uso_dos_formularios na wiki anteriormente].
+
noopener </syntaxhighlight>
 +
: evita que a nova xanela poida acceder á xanela orixinal mediante `window.opener`.
  
−
:: Mysql incorpora tamén unha función para codificar os caracteres (como espazos en branco, saltos de liña,...[http://php.net/manual/es/mysqli.real-escape-string.php Función string mysqli::real_escape_string ( string $escapestr )]. Devolve a cadea 'codificada'.
+
'''NOTA:''' O navegador ten a decisión final sobre se a páxina se abre nunha nova xanela ou nunha pestana e pode ignorar algunhas características solicitadas.
−
:: Por exemplo:
 
−
:::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
$cidade = "'s Hertogenbosch";
 
  
−
/* esta consulta fallará debido a que no escapa $ciudad */
 
−
if (!$mysqli->query("SELECT nome FROM CIDADES where nome_cidade = '$cidade'")) {
 
−
    printf("Erro: %s\n", $mysqli->sqlstate);
 
−
}
 
  
−
$cidade = $mysqli->real_escape_string($cidade);
 
  
−
/* esta consulta con $ciudad escapada funcionará */
+
<br />
−
if (!$mysqli->query("SELECT nome FROM CIDADES where nome_cidade = '$cidade'")) {
+
'''Vantaxe de empregar un href real'''
−
    printf("Fila inxerida.\n");
 
−
}
 
  
−
</syntaxhighlight>
+
* Non empregamos:
−
::Exemplo obtido de php.net
+
::<syntaxhighlight lang="html"> <a href="#"> </syntaxhighlight>
  
 +
* Empregamos:
 +
::<syntaxhighlight lang="html"> <a href="detalle_libro.php?libro=5"> </syntaxhighlight>
  
−
* Pode suceder que un mesmo autor teña varios libros. Como imos a amosar os datos dentro dun grid no que na primeira columna vai ser o nome do autor e a segunda o nome do libro, vai repetirse o nome do autor como se amosa na seguinte imaxe:
+
: Desta forma, se JavaScript non funciona ou está desactivado, o enlace segue sendo válido e o usuario poderá acceder igualmente ao detalle do libro.
  
−
[[Imagen:Php_BD_10.jpg|500px|center]]
 
  
−
:: Para evitalo, teremos que gardar en cada iteración unha vez 'impresa' a liña do autor, o nome do autor e comparalo cando carguemos a seguinte fila de datos. Se o nome do autor da nova liña de datos é igual ao anterior, entón non o debemos de amosar.
 
  
−
:: O código sería algo parecido a isto:
+
<br />
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
'''Páxina detalle_libro.php'''
−
                    $autor='';  // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                    $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                    while($row = $result->fetch_assoc()) {    // PROCESAMOS AS FILAS DO RESULTADO DA CONSULTA
 
−
                        if ($last_autor==$row["nome_autor"]){  // Comprobamos se o novo autor é igual ao anterior procesado
 
−
                            $autor='';          // Se é igual entón quere dicir que estamos a ler unha fila co mesmo autor que a anterior. Polo tanto non 'imprimimos o autor'
 
−
                        }
 
−
                        else {
 
−
                            $autor=$row["nome_autor"];  // En caso contrario quere dicir que o autor actual é diferente ao anterior e polo tanto debemos sacalo por pantalla.
 
−
                        }
 
  
−
                        printf(AMOSAMOS OS DATOS DO LIBRO E DA VARIABLE $autor);
+
* A páxina de detalle recibirá o identificador do libro mediante:
  
−
                        $last_autor=$row["nome_autor"];  // Gardamos o nome do autor para comparar có autor que veña na seguinte fila de datos.
+
::<syntaxhighlight lang="text">$_GET['libro'] </syntaxhighlight>
−
                    }
 
  
−
</syntaxhighlight>
+
* Como é un dato externo, debemos validalo:
  
 +
::<syntaxhighlight lang="php">
 +
$libroId = filter_input(
 +
INPUT_GET,
 +
'libro',
 +
FILTER_VALIDATE_INT
 +
);
  
 +
if (
 +
  $libroId === false ||
 +
  $libroId === null ||
 +
  $libroId < 1
 +
) {
  
−
* Imos facer que cada libro sexa un enlace para abrir unha páxina nova de nome '''detalle_libro.php'''. No resultado da busca soamente imos amosar o nome do libro, de tal forma que cando o usuario preme sobre o enlace, apareza unha nova xanela cos datos completos dese libro, incluíndo os exemplares).
+
  die('Identificador de libro non válido.');
  
−
:: Para facelo imos facer uso de javascript e da [http://www.w3schools.com/jsref/met_win_open.asp función window.open].
+
}  
−
:: Crearei unha función en javascript que espere recibir un parámetro, que sexa a url a abrir e chamarei a window.open cunha serie de modificacións, como o tamaño da xanela, que non poida modificar a url,...
+
</syntaxhighlight>
−
:: O código será o seguinte:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="4-8" >
 
−
    <head>
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Operacións BD</title>
 
−
        <script type="text/javascript">
 
−
            function abrirVentana($url){
 
−
                window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
 
−
            }
 
−
        </script>
 
−
       
 
−
    </head>
 
−
</syntaxhighlight>  
 
  
  
−
:: Para chamar a esta función usaremos a etiqueta <a> e o evento onclick, coma o seguinte exemplo:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="4-8" >
 
−
<a href='#' onclick="javascript:abrirVentana('detalle_libro.php?libro=1');">Título Libro</a>
 
−
</syntaxhighlight>
 
  
−
:: <u>Hai que ter moito coidado cando estamos a traballar con PHP-Javascript polo tema das comillas xa que teremos que 'xogar' coas simples e dobres para que se pechen correctamente</u>.
+
* A consulta posterior tamén deberá ser preparada:
 +
::<syntaxhighlight lang="php">
 +
$stmt = $conex->prepare(
 +
  'SELECT id, titulo
 +
  FROM libros
 +
  WHERE id = :libro_id'
 +
);
  
 +
$stmt->bindValue(':libro_id',$libroId,PDO::PARAM_INT);
 +
$stmt->execute();
  
 +
$libro = $stmt->fetch();
 +
</syntaxhighlight>
  
 +
* Se queremos recuperar tamén os exemplares dese libro faremos outra consulta preparada:
  
 +
::<syntaxhighlight lang="php">
 +
$stmt = $conex->prepare(
 +
'SELECT id_exemplar,imaxe,prezo,estado_id
 +
  FROM exemplares
 +
  WHERE libro_id = :libro_id
 +
  ORDER BY id_exemplar'
 +
);
  
−
<br />
+
$stmt->bindValue(':libro_id',$libroId,PDO::PARAM_INT);
−
* '''SOLUCIÓN'''
+
$stmt->execute();
−
 
 
−
'''Arquivo: bd.inc.php'''
 
−
 
 
−
<u>Nota:</u> Definimos a consulta a realizar á base de datos ('''lembrar que é mellor facer dita consulta directamente no Mysql e despois copiala ao código PHP pasando os parámetros correspondentes''')
 
−
::<syntaxhighlight lang="php" enclose="div">
 
−
<?php
 
  
−
define ('CONSULTAR_LIBROS_POR_AUTOR',"select CONCAT(apelidos,',',nome) as nome_autor,libros.id ,titulo from libros " .
+
$exemplares = $stmt->fetchAll();  
−
        ' INNER JOIN libro_autor ON (libro_autor.libro_id=libros.id) ' .
 
−
        ' INNER JOIN autores ON (libro_autor.autor_id=autores.id)' .
 
−
        " where CONCAT(autores.nome,' ',autores.apelidos) like '%%%s%%' " .
 
−
        'order by nome_autor,titulo');
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
: <u>Nota:</u> Fixarse como o parámetro que recibe 'like' é de tipo cadea e polo tanto vai entre comillas e leva dobre % para que escriba o % non o interprete como un parámetro do printf.
+
===== Resumo do proceso =====
−
: É dicir, nos o que queremos é que execute a sentenza: like '%palabraabuscar%' pero esa 'palabra a buscar' vai ser %s (un parámetro cadea a empregar en printf). Entón non podemos poñer %%s% xa que printf interpretará eses % como parámetros a enviar. Temos que decirlle que o % inicial e final non debe interpretalos como parámetro e iso se fai poñenedo %%.
+
 
−
 
+
::<syntaxhighlight lang="text">
 +
$_POST['txtAutor']
 +
↓
 +
trim()
 +
↓
 +
validación
 +
↓
 +
prepare()
 +
↓
 +
bindValue(PDO::PARAM_STR)
 +
↓
 +
execute()
 +
↓
 +
fetchAll()
 +
↓
 +
htmlspecialchars()
 +
↓
 +
enlace ao libro
 +
↓
 +
window.open()
 +
↓
 +
detalle_libro.php?libro=ID
 +
↓
 +
FILTER_VALIDATE_INT
 +
↓
 +
prepare() + PDO::PARAM_INT </syntaxhighlight>
 +
 
 +
</syntaxhighlight>
 +
 
 +
 
 +
* Debemos diferenciar claramente:
 +
 
 +
::<syntaxhighlight lang="text">
 +
 
 +
! Operación
 +
 
 +
| ! Finalidade                                          |
 +
| ------------------------------------------------------ |
 +
| `trim()` e comprobación da lonxitude                  |
 +
| Validar os datos segundo as regras da aplicación.      |
 +
| -                                                      |
 +
| `prepare()` + `bindValue()`                            |
 +
| Separar os datos da estrutura da sentenza SQL.        |
 +
| -                                                      |
 +
| `PDO::PARAM_STR`                                      |
 +
| Indicar que o parámetro enviado é unha cadea.         |
 +
| -                                                      |
 +
| `htmlspecialchars()`                                  |
 +
| Escapar datos cando se introducen dentro de HTML.     |
 +
| -                                                      |
 +
| `window.open()`                                        |
 +
| Abrir o detalle do libro nunha nova xanela ou pestana. |
 +
| -                                                      |
 +
| `FILTER_VALIDATE_INT`                                  |
 +
| Validar o identificador recibido por `GET`.           |
 +
| }                                                      |
 +
</syntaxhighlight>
  
  
  
 
<br />
 
<br />
−
'''Arquivo buscar_libros_por_autor.php'''
 
−
::<syntaxhighlight lang="php" 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');
+
=== Consulta chamando a procedementos almacenados ===
 +
 
 +
* Un procedemento almacenado permite gardar no propio xestor de base de datos un conxunto de sentenzas SQL que poden ser executadas posteriormente.
  
−
try {
+
* Desde PDO chamarémolo mediante a sentenza:
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); 
 
−
    $conex->set_charset('utf8mb4');
 
  
−
$erro="";  // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
+
::<syntaxhighlight lang="sql">
−
?>
+
CALL NOME_PROCEDEMENTO() </syntaxhighlight>
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
* Por exemplo, en MySQL:
−
    "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>Operacións BD</title>
 
−
        <script type="text/javascript">
 
−
            function abrirVentana($url){
 
−
                window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
 
−
            }
 
−
        </script>
 
−
       
 
−
    </head>
 
  
−
    <body>
+
::<syntaxhighlight lang="sql">
−
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
+
DELIMITER $$
−
            Introduce o nome do autor a buscar:
 
−
            <input type='text' size='50' maxlength="100" required name="txtAutor" value='<?php echo (isset($_POST['txtAutor']) ? htmlentities($_POST["txtAutor"]):""); ?>'/>
 
−
            <input type="submit" value="BUSCAR" />
 
−
            <input type='button' onclick="javascript:this.form['txtAutor'].value='';" value="LIMPAR" />
 
−
        <?php       
 
−
        if (!empty($_POST['txtAutor'])){  // Premimos o botón de busca
 
−
            $textoBuscar = filter_var($_POST['txtAutor'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); // Eliminamos da cadea de busca todos os caracteres non permitidos
 
−
            if (strlen($textoBuscar)<3){  // É un exemplo de control de erros. Indico que o texto a buscar ten que ter a lo menos 3 caracteres
 
−
                $erro="<h2>A cadea a buscar non pode ser menor de 3 caracteres</h2>";
 
−
            }
 
−
            else {
 
−
                $query=sprintf(CONSULTAR_LIBROS_POR_AUTOR,$conex->real_escape_string($textoBuscar));
 
−
                $result=$conex->query($query);
 
−
                if ($result->num_rows>0){
 
−
                    $autor='';  // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                    $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                    echo "<table border='1'>";
 
−
                    echo "<th>Autor</th><th>Libro</th>";
 
−
                    while($row = $result->fetch_assoc()) {
 
−
                        if ($last_autor==$row["nome_autor"]){
 
−
                            $autor='';  // O autor é o mesmo e non queremos que se repita na táboa
 
−
                        }
 
−
                        else {
 
−
                            $autor=htmlentities($row["nome_autor"]);
 
−
                        }
 
−
                        echo '<tr>';
 
−
                        $onclick=sprintf("javascript:abrirVentana(\"detalle_libro.php?libro=%d\");",htmlentities($row["id"]));
 
−
                        printf("<td>%s</td><td><a href='#' onclick='%s'>%s</a></td>",$autor,$onclick,htmlentities($row['titulo']));
 
−
                        echo '</tr>';
 
−
                        $last_autor=$row["nome_autor"];
 
−
                    }
 
−
                    $result->free();
 
−
                    echo "</table>";
 
−
                }
 
−
                else {
 
−
                    $erro = "<br />Non hai resultados coa cadea $textoBuscar";
 
−
                }
 
−
               
 
−
               
 
−
            }
 
−
        }
 
−
       
 
−
        ?>       
 
−
        </form>
 
−
        <?php       
 
−
        if ($erro!='') {
 
−
            printf("<h3>%s</h3>",$erro);
 
−
        }
 
−
        ?>       
 
−
       
 
−
       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
CREATE PROCEDURE CONSULTAR_LIBROS()
−
}  // Pecha o try da parte superior da páxina
+
BEGIN
−
catch (mysqli_sql_exception $e) {
 
−
    die("Erro mysql: " . $e->getMessage());
 
−
}
 
−
catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
−
* Fixarse que nesta páxina non necesitamos conectar ao principio, xa que soamente se carga a caixa de texto para que o usuario introduza o nome do autor a buscar.
+
SELECT id, titulo
−
:: Polo tanto a conexión e o peche da base de datos se podería poñer dentro do else da liña 43.
+
FROM libros
−
* Fixarse que o botón de limpar non é de tipo 'reset' xa que ao cargar o que veña en $_POST o botón tipo reset volvería a amosar dito valor.
+
ORDER BY titulo;
  
 +
END$$
  
−
* '''Resultado:'''
+
DELIMITER ; </syntaxhighlight>
−
[[Imagen:Php_BD_11.jpg|500px|center]]
 
  
 +
==== Empregando PDO ====
  
 +
::<syntaxhighlight lang="php">
 +
$stmt = $conex->prepare(
 +
'CALL CONSULTAR_LIBROS()'
 +
);
  
−
<br />
+
$stmt->execute();
  
−
<br />
+
$libros = $stmt->fetchAll();
−
=====Empregando PDO=====
 
  
−
* Imos crear unha páxina no que buscaremos libros en base a un nome do autor.
+
$stmt->closeCursor(); </syntaxhighlight>
−
:: A busca farase utilizando a [http://www.w3schools.com/sql/sql_like.asp orde SQL '''like'''], que busca patróns de cadea. Este operador non distingue maiúsculas de minúsculas.
 
−
:: Se queremos distinguir maiúsculas de minúsculas utilizaremos o operador [http://stackoverflow.com/questions/14007450/how-do-you-force-mysql-like-to-be-case-sensitive like binary].
 
−
:: O operador like o imos aplicar ao nome completo do autor, polo que teremos que concatenar o nome e apelido. Usaremos a función [http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat-ws concat_ws] ou [http://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_concat concat].
 
  
 +
* `closeCursor()` libera os resultados pendentes asociados á chamada.
  
−
* O usuario introducirá nunha caixa de texto o nome (ou parte del) do autor.  
+
: É especialmente recomendable ao traballar con procedementos almacenados en MySQL antes de executar outra consulta na mesma conexión.
  
−
: <u>Nota:</u> Lembra que no paso de datos temos que ter en conta os posibles 'ataques' que poidan intentar facerse. Repasar o dado [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Seguridade_no_uso_dos_formularios na wiki anteriormente].
+
==== Caso Práctico: Consulta chamando a procedementos almacenados ====
−
: Empregando PDO <u>non necesitamos escapar os parámetros</u> xa que o fai el automaticamente.
 
  
 +
* Definimos o nome do procedemento:
  
 +
::<syntaxhighlight lang="php">
 +
define(
 +
'PROCEDEMENTO_CONSULTAR_LIBROS',
 +
'CONSULTAR_LIBROS'
 +
); </syntaxhighlight>
  
−
* Pode suceder que un mesmo autor teña varios libros. Como imos a amosar os datos dentro dun grid no que na primeira columna vai ser o nome do autor e a segunda o nome do libro, vai repetirse o nome do autor como se amosa na seguinte imaxe:
+
* Realizamos a chamada:
  
−
[[Imagen:Php_BD_10.jpg|500px|center]]
+
::<syntaxhighlight lang="php">
 +
$stmt = $conex->prepare(
 +
'CALL ' .
 +
PROCEDEMENTO_CONSULTAR_LIBROS .
 +
'()'
 +
);
  
−
:: Para evitalo, teremos que gardar en cada iteración unha vez 'impresa' a liña do autor, o nome do autor e comparalo cando carguemos a seguinte fila de datos. Se o nome do autor da nova liña de datos é igual ao anterior, entón non o debemos de amosar.
+
$stmt->execute();
  
−
:: O código sería algo parecido a isto:
+
$libros = $stmt->fetchAll();
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
                    $autor='';  // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                    $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                    while ($row = $result->fetch(PDO::FETCH_ASSOC)) {      // PROCESAMOS AS FILAS DO RESULTADO DA CONSULTA
 
−
                        if ($last_autor==$row["nome_autor"]){  // Comprobamos se o novo autor é igual ao anterior procesado
 
−
                            $autor='';          // Se é igual entón quere dicir que estamos a ler unha fila co mesmo autor que a anterior. Polo tanto non 'imprimimos o autor'
 
−
                        }
 
−
                        else {
 
−
                            $autor=$row["nome_autor"]; // En caso contrario quere dicir que o autor actual é diferente ao anterior e polo tanto debemos sacalo por pantalla.
 
−
                        }
 
  
−
                        printf(AMOSAMOS OS DATOS DO LIBRO E DA VARIABLE $autor);
+
$stmt->closeCursor(); </syntaxhighlight>
  
−
                        $last_autor=$row["nome_autor"];  // Gardamos o nome do autor para comparar có autor que veña na seguinte fila de datos.
+
* Non necesitamos comprobar o resultado de `execute()` para saber se o `SELECT` devolveu filas.
−
                    }
 
  
−
</syntaxhighlight>
+
: `execute()` indica se a sentenza puido executarse.
  
 +
: Para saber se temos resultados comprobamos os datos recuperados:
  
 +
::<syntaxhighlight lang="php">
 +
if ($libros) {
  
−
* Imos facer que cada libro sexa un enlace para abrir unha páxina nova de nome '''detalle_libro.php'''. No resultado da busca soamente imos amosar o nome do libro, de tal forma que cando o usuario preme sobre o enlace, apareza unha nova xanela cos datos completos dese libro, incluíndo os exemplares).
 
  
−
:: Para facelo imos facer uso de javascript e da [http://www.w3schools.com/jsref/met_win_open.asp función window.open].
+
// Hai resultados
−
:: Crearei unha función en javascript que espere recibir un parámetro, que sexa a url a abrir e chamarei a window.open cunha serie de modificacións, como o tamaño da xanela, que non poida modificar a url,...
 
−
:: O código será o seguinte:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="4-8" >
 
−
    <head>
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Operacións BD</title>
 
−
        <script type="text/javascript">
 
−
            function abrirVentana($url){
 
−
                window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
 
−
            }
 
−
        </script>
 
−
       
 
−
    </head>
 
−
</syntaxhighlight>
 
  
 +
} else {
  
−
:: Para chamar a esta función usaremos a etiqueta <a> e o evento onclick, coma o seguinte exemplo:
+
// Non hai resultados
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
<a href='#' onclick="javascript:abrirVentana('detalle_libro.php?libro=1');">Título Libro</a>
 
−
</syntaxhighlight>
 
  
−
:: <u>Hai que ter moito coidado cando estamos a traballar con PHP-Javascript polo tema das comillas xa que teremos que 'xogar' coas simples e dobres para que se pechen correctamente</u>.
+
} </syntaxhighlight>
  
 +
<br />
  
 +
=== Consulta chamando a procedementos almacenados con parámetros de entrada ===
  
 +
* Os parámetros dun procedemento almacenado poden ser:
  
 +
:* `IN`: parámetros de entrada.
 +
:* `OUT`: parámetros de saída.
 +
:* `INOUT`: funcionan como entrada e saída.
  
−
<br />
+
* Por exemplo:
−
* '''SOLUCIÓN'''
 
  
−
'''Arquivo: bd.php'''
+
::<syntaxhighlight lang="sql">
 +
DELIMITER $$
  
−
<u>Nota:</u> Definimos a consulta a realizar á base de datos ('''lembrar que é mellor facer dita consulta directamente no Mysql e despois copiala ao código PHP pasando os parámetros correspondentes''')
+
CREATE PROCEDURE PROCEDEMENTO_CON_PARAMETROS(
−
::<syntaxhighlight lang="php" enclose="div">
+
IN p_num INT,
−
<?php
+
IN p_text VARCHAR(100)
 +
)
 +
BEGIN
  
−
define ('CONSULTAR_LIBROS_POR_AUTOR',"select CONCAT(apelidos,',',nome) as nome_autor,libros.id ,titulo from libros " .
+
SELECT col1, col2
−
        ' INNER JOIN libro_autor ON (libro_autor.libro_id=libros.id) ' .
+
FROM TABOA_1
−
        ' INNER JOIN autores ON (libro_autor.autor_id=autores.id)' .
+
WHERE col1 = p_num
−
        " where CONCAT(autores.nome,' ',autores.apelidos) like :autor" .
+
  AND col2 = p_text;
−
        ' order by nome_autor,titulo');
 
−
</syntaxhighlight>
 
  
−
: <u>Nota:</u> Fixarse como o parámetro que recibe 'like' é de tipo cadea e polo tanto vai entre comillas. Ao empregar parámetros con PDO el xa se encarga de poñer comillas.
+
END$$
−
: '''A complicación é que nos queremos enviar %valor% para que busque por patrón. Iso o facemos ao darlle o valor ao parámetro.'''
 
  
 +
DELIMITER ; </syntaxhighlight>
  
 +
==== Empregando PDO ====
  
 +
* Preparamos a chamada igual que calquera outra sentenza parametrizada:
  
−
<br />
+
::<syntaxhighlight lang="php">
−
'''Arquivo buscar_libros_por_autor.php'''
+
$stmt = $conex->prepare(
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
'CALL PROCEDEMENTO_CON_PARAMETROS(
−
<?php
+
:param_num,
−
/* Lembrar quitar isto ao probar o scripts sen erros */
+
:param_text
−
ini_set('display_errors', 1);
+
)'
−
ini_set('display_startup_errors', 1);
+
);
−
error_reporting(E_ALL);
 
  
−
require ('/var/www/confBD/bd.php');
+
$stmt->bindValue(
 +
':param_num',
 +
$valorNum,
 +
PDO::PARAM_INT
 +
);
  
−
try {
+
$stmt->bindValue(
−
    $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8mb4",SERVIDOR,BD);
+
':param_text',
−
    $conex = new PDO($dsn, USUARIO, PASSWORD);
+
$valorTexto,
−
    $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
PDO::PARAM_STR
 +
);
  
−
    $erro="";   // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
+
$stmt->execute();
−
?>
 
  
 +
$filas = $stmt->fetchAll();
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
$stmt->closeCursor(); </syntaxhighlight>
−
    "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>Operacións BD</title>
 
−
        <script type="text/javascript">
 
−
            function abrirVentana($url){
 
−
                window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
 
−
            }
 
−
        </script>
 
−
       
 
−
    </head>
 
  
−
    <body>
+
* Desta forma non necesitamos empregar `sprintf()` nin concatenar os valores dentro da chamada SQL.
−
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
 
−
            Introduce o nome do autor a buscar:
 
−
            <input type='text' size='50' maxlength="100" required name="txtAutor" value='<?php echo (isset($_POST['txtAutor']) ? htmlentities($_POST["txtAutor"]):""); ?>'/>
 
−
            <input type="submit" value="BUSCAR" />
 
−
            <input type='button' onclick="javascript:this.form['txtAutor'].value='';" value="LIMPAR" />
 
−
        <?php       
 
−
        if (!empty($_POST['txtAutor'])){  // Premimos o botón de busca
 
−
            $textoBuscar = filter_var($_POST['txtAutor'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); // Eliminamos da cadea de busca todos os caracteres non permitidos
 
−
            if (strlen($textoBuscar)<3){  // É un exemplo de control de erros. Indico que o texto a buscar ten que ter a lo menos 3 caracteres
 
−
                $erro="<h2>A cadea a buscar non pode ser menor de 3 caracteres</h2>";
 
−
            }
 
−
            else {
 
−
                $stmt=$conex->prepare(CONSULTAR_LIBROS_POR_AUTOR);    // Temos que poñer prepare
 
−
                $autor = $_POST['txtAutor'];
 
−
                $result = $stmt->execute(['autor' => "%$autor%"]);
 
  
−
                if ($result){
+
==== Caso Práctico: buscar libros por autor ====
−
                    $autor='';  // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                    $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                    echo "<table border='1'>";
 
−
                    echo "<th>Autor</th><th>Libro</th>";
 
−
                    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
 
−
                        if ($last_autor==$row["nome_autor"]){
 
−
                            $autor='';  // O autor é o mesmo e non queremos que se repita na táboa
 
−
                        }
 
−
                        else {
 
−
                            $autor=htmlentities($row["nome_autor"]);
 
−
                        }
 
−
                        echo '<tr>';
 
−
                        $onclick=sprintf("javascript:abrirVentana(\"detalle_libro.php?libro=%d\");",htmlentities($row["id"]));
 
−
                        printf("<td>%s</td><td><a href='#' onclick='%s'>%s</a></td>",$autor,$onclick,htmlentities($row['titulo']));
 
−
                        echo '</tr>';
 
−
                        $last_autor=$row["nome_autor"];
 
−
                    }
 
−
                    $result = null;
 
−
                    echo "</table>";
 
−
                }
 
−
                else {
 
−
                    $erro = "<br />Non hai resultados coa cadea $textoBuscar";
 
−
                }
 
−
               
 
−
               
 
−
            }
 
−
        }
 
−
       
 
−
        ?>       
 
−
        </form>
 
−
        <?php       
 
−
        if ($erro!='') {
 
−
            printf("<h3>%s</h3>",$erro);
 
−
        }
 
−
        ?>       
 
−
       
 
−
       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
* Lembrar que o usuario que se conecta á base de datos dende php ten que ter permiso execute sobre a base de datos.
−
}    // Pecha o try do principio da páxina
+
::<syntaxhighlight lang="sql">
−
catch (PDOException $e) {
+
grant execute on php.* to 'user_php'@'localhost';
−
    die("Erro PDO: " . $e->getMessage());
+
flush privileges;
−
} catch (Exception $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
} finally {
 
−
    $conex = null; // Pechar a conexión
 
−
}
 
−
?>
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
−
* Fixarse que nesta páxina non necesitamos conectar ao principio, xa que soamente se carga a caixa de texto para que o usuario introduza o nome do autor a buscar.
 
−
:: Polo tanto a conexión e o peche da base de datos se podería poñer dentro do else da liña 44.
 
−
* Fixarse que o botón de limpar non é de tipo 'reset' xa que ao cargar o que veña en $_POST o botón tipo reset volvería a amosar dito valor.
 
  
 +
* Procedemento almacenado (isto tedes que executalo en Mysql empregando a ferramenta Mysql WorkBench ou PhpMyAdmin):
  
−
* '''Resultado:'''
+
::<syntaxhighlight lang="sql">
−
[[Imagen:Php_BD_11.jpg|500px|center]]
+
DELIMITER $$
  
 +
CREATE PROCEDURE CONSULTAR_LIBROS_POR_AUTOR(
 +
IN p_autor_buscar VARCHAR(100)
 +
)
 +
BEGIN
  
−
<br />
+
SELECT CONCAT(
 +
          autores.apelidos,
 +
          ', ',
 +
          autores.nome
 +
      ) AS nome_autor,
 +
      libros.id AS id_libro,
 +
      libros.titulo
 +
FROM libros
 +
INNER JOIN libro_autor
 +
    ON libro_autor.libro_id = libros.id
 +
INNER JOIN autores
 +
    ON libro_autor.autor_id = autores.id
 +
WHERE CONCAT(
 +
          autores.nome,
 +
          ' ',
 +
          autores.apelidos
 +
      ) LIKE CONCAT(
 +
          '%',
 +
          p_autor_buscar,
 +
          '%'
 +
      )
 +
ORDER BY nome_autor, libros.titulo;
  
−
=== Consulta chamando a procedementos almacenados ===
+
END$$
  
−
* Un procedemento almacenado ven a ser o equivalente a unha función en PHP, coa diferenza que as ordes que conforman o procedemento van permitir realizar accións (inserción, borrado, modificación e selección) sobre a base de datos.
+
DELIMITER ; </syntaxhighlight>
  
−
* Como comentamos [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Soluci.C3.B3n_a_inxecci.C3.B3n_de_CSS anteriormente nesta wiki], unha das mellores formas de evitar ataques de tipo 'inxección de CSS' é utilizar procedementos almacenados.
+
* Chamada desde PDO cambiando no exercicio anterior a chamada ao SELECT pola chamada a este procedemento almacenado:
  
−
: Tamén é unha forma de aumentar a seguridade de acceso á base de datos Mysql, xa que teremos que dar permiso de execución sobre o procedemento almacenado e o usuario non vai poder acceder ás táboas da base de datos nin examinar a estrutura das mesmas.
+
::<syntaxhighlight lang="php" highlight="23-25,58">
−
: Desta forma podemos cambiar a estrutura da táboa (como por exemplo cambiar o nome dunha columna) e simplemente cambiando a sentenza SQL que está dentro do procedemento almacenado e poñendo un alias có nome antigo, a aplicación no cliente segue funcionando.
+
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
−
: Outra vantaxe é que as sentencias SQL non están definidas nas páxinas PHP e polo tanto se realizamos un cambio o faremos no Mysql sendo 'transparente' cara ao usuario.
 
  
 +
        /*
 +
        * Comprobamos que se introduciu algún texto.
 +
        */
 +
        if ($textoBuscar === '') {
  
−
* Máis información [http://php.net/manual/es/mysqli.quickstart.stored-procedures.php neste enlace].
+
            $erro = 'Debes introducir un texto para realizar a busca.';
  
−
* O primeiro que temos que facer é crear o procedemento almacenado.
+
            /*
−
* A orde SQL é [http://dev.mysql.com/doc/refman/5.7/en/create-procedure.html '''CREATE PROCEDURE'''].
+
        * Como regra da aplicación esiximos
 +
        * polo menos 3 caracteres.
 +
        */
 +
        } elseif (mb_strlen($textoBuscar) < 3) {
  
−
* Creamos o procedemento almacenado:
+
            $erro =
−
:* Cando definimos un procedemento almacenado debemos facer uso da palabra DELIMITER para indicarlle ao Mysql onde acaba a definición do procedemento almacenado. Máis información [http://dev.mysql.com/doc/refman/5.7/en/stored-programs-defining.html neste enlace].
+
                'A cadea a buscar debe ter polo menos 3 caracteres.';
 +
        } else {
  
−
'''Creamos un procedemento almacenado dentro do MysqlWorkBench'''
+
            /*
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
            * Preparamos a consulta.
−
DELIMITER $$
+
            */
 +
            $stmt = $conex->prepare(
 +
                'call CONSULTAR_LIBROS_POR_AUTOR(:autor)'
 +
            );
  
−
CREATE PROCEDURE `NOME_PROCEDEMENTO` ()
 
−
BEGIN
 
  
−
  select col1,col2
+
            /*
−
  from TABOA_1
+
            * Engadimos os comodíns % de LIKE ao valor
−
  order by col1;
+
            * que enviamos ao parámetro.
 +
            *
 +
            * Por exemplo:
 +
            *
 +
            * gar
 +
            *
 +
            * convértese en:
 +
            *
 +
            * %gar%
 +
            */
 +
            $stmt->bindValue(
 +
                ':autor',
 +
                '%' . $textoBuscar . '%',
 +
                PDO::PARAM_STR
 +
            );
  
  
−
END
+
            /*
−
$$
+
            * Executamos a consulta.
−
</syntaxhighlight>  
+
            */
 +
            $stmt->execute();
  
−
: Como vemos o procedemento está formado por unha consulta SELECT (pode poñerse calquera)
 
  
 +
            /*
 +
            * Recuperamos todos os resultados.
 +
            */
 +
            $libros = $stmt->fetchAll();
  
−
* Agora dende PHP temos que facer uso do comando CALL 'NOME_PROCEDEMENTO'.
+
            $stmt->closeCursor();
  
  
−
<br />
+
            /*
−
====Orientado a obxectos====
+
            * Se non hai filas gardamos unha mensaxe
−
: Isto é o que teriamos que poñer se dende Mysql queredes chamar ao procedemento....
+
            * para amosala posteriormente.
 +
            */
 +
            if (!$libros) {
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="12" >
+
                $erro =
−
<?php
+
                    'Non se atoparon libros para ese autor.';
−
/* Lembrar quitar isto ao probar o script sen erros */
+
            }
−
ini_set('display_errors', 1);
+
        }
−
ini_set('display_startup_errors', 1);
+
    }
−
error_reporting(E_ALL);
+
</syntaxhighlight>
  
−
require ('/var/www/confBD/bd.inc.php');  // Onde están definidas as constantes que se empregan na conexión (feito nas seccións anteriores)
+
<br />
  
−
try {
+
=== Consulta chamando a procedementos almacenados con parámetros de saída ===
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); 
 
−
    $conex->set_charset('utf8mb4');
 
−
    $proc = sprintf("CALL %s",NOME_PROCEDEMENTO);
 
  
−
} 
+
* Un parámetro `OUT` permite que un procedemento devolva un valor.
−
catch (mysqli_sql_exception $e) {
 
−
    die("Erro mysql: " . $e->getMessage());
 
−
}
 
−
catch (Expcetion $e) {
 
−
    die("Erro xeral: " . $e->getMessage());
 
−
}
 
−
finally{
 
−
  if(!empty($conex)){
 
−
      $conex->close();
 
−
  }
 
−
}
 
  
 +
* Por exemplo:
  
−
</syntaxhighlight>  
+
::<syntaxhighlight lang="sql">
 +
DELIMITER $$
  
 +
CREATE PROCEDURE CONSULTAR_NUM_LIBROS(
 +
OUT p_num_libros INT
 +
)
 +
BEGIN
  
 +
SELECT COUNT(*)
 +
INTO p_num_libros
 +
FROM libros;
  
−
* A diferenza do caso anterior, cando chamemos a un procedemento almacenado teremos que facer uso dunha das seguintes funcións:
+
END$$
−
:: [http://php.net/manual/es/mysqli.real-query.php mysqli::real_query] ou [http://php.net/manual/es/mysqli.multi-query.php mysqli::multi_query]
 
  
−
: Nos imos facer uso da chamada a '''multi_query''' xa que tamén a imos poder utilizar cando dentro dun mesmo procedemento almacenado teñamos varios select´s e queiramos recuperar o contido de todos eles (cada un será un conxunto de resultados).
+
DELIMITER ; </syntaxhighlight>
−
::<syntaxhighlight lang="php" enclose="div" highlight="3,4" >
 
−
        ........................
 
−
        $proc = sprintf("CALL %s",NOME_PROCEDEMENTO);
 
−
        if ($conex->multi_query($proc)){
 
−
            $result = $conex->store_result();
 
−
            if ($result && ($result->num_rows>0)){
 
−
                ................
 
−
            }
 
  
−
        }
+
* Con PDO e MySQL podemos empregar unha variable de usuario de MySQL:
  
−
</syntaxhighlight>  
+
::<syntaxhighlight lang="php">
−
 
+
$stmt = $conex->prepare(
−
 
+
'CALL CONSULTAR_NUM_LIBROS(
−
: Unha vez temos o conxunto de resultados ($result) xa operamos como nos casos anteriores.
+
@numero_libros
 +
)'
 +
);
  
 +
$stmt->execute();
  
−
*<u>IMPORTANTE</u> Se estamos a utilizar procedementos almacenados, pode darse o caso de que despois de chamar a un procedemento que devolva <u>un conxunto de resultados</u>, ao intentar chamar a outro procedemento vos dea un erro '2014 - Commands out of sync; you can't run this command now' ou simplemente a chamada ao seguinte procedemento non vos devolva resultados.
+
/*
−
: A resposta a ese erro a tedes [https://dev.mysql.com/doc/refman/8.0/en/commands-out-of-sync.html neste enlace].
 
  
 +
* É importante liberar os resultados
 +
* da chamada antes de executar outro SELECT.
 +
  */
 +
  $stmt->closeCursor();
  
 +
$stmt = $conex->query(
 +
'SELECT @numero_libros'
 +
);
  
−
* Ao parecer, existen aínda conxuntos de resultados sen 'recoller' (aínda que o procedemento soamente devolva un) e non vos deixará realizar outra chamada ata que leades todos os conxuntos de resultados.
+
$numLibros = (int) $stmt->fetchColumn(); </syntaxhighlight>
−
: Para facelo, podedes engadir o seguinte código:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="9" >
 
−
        ........................
 
−
        $proc = sprintf("CALL %s",NOME_PROCEDEMENTO);
 
−
        if ($conex->multi_query($proc)){
 
−
            $result = $conex->store_result();
 
−
            if ($result && ($result->num_rows>0)){
 
−
                ................
 
−
                // Procesamos cada fila
 
−
            }
 
−
            while($conex->next_result() && $conex->store_result());
 
  
−
        }
+
* Podemos amosar o resultado:
  
−
</syntaxhighlight>  
+
::<syntaxhighlight lang="php">
 +
printf(
 +
'<h1>Número de libros á venda: %d</h1>',
 +
$numLibros
 +
); </syntaxhighlight>
  
 +
<br />
  
 +
=== Consulta chamando a funcións ===
  
 +
* MySQL tamén permite definir funcións almacenadas.
  
−
* Como comentei anteriormente, un procedemento pode estar conformado por múltiples 'select'.
+
* Unha función, a diferenza dun procedemento, devolve directamente un valor e pode empregarse dentro dunha expresión SQL.
−
: Se queremos 'pasar' ao seguinte 'conxunto de resultados' temos que chamar á [http://php.net/manual/es/mysqli.next-result.php función mysqli::next_result] desta forma:
 
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="8-10" >
+
* Por exemplo:
−
        ........................
 
−
        $proc = sprintf("CALL %s",NOME_PROCEDEMENTO);
 
−
        if ($conex->multi_query($proc)){
 
−
            $result = $conex->store_result();
 
−
            if ($result && ($result->num_rows>0)){
 
−
                ................
 
−
            }
 
−
            if ($conex->next_result()){
 
−
              $result=$conex->store_result();
 
−
            }
 
  
−
        }
+
::<syntaxhighlight lang="sql">
 +
DELIMITER $$
  
−
</syntaxhighlight>
+
CREATE FUNCTION CONSULTAR_NUM_LIBROS()
 +
RETURNS INT
 +
READS SQL DATA
 +
BEGIN
  
−
: Se imos procesar todos os conxuntos de resultados da mesma forma poderíamos utilizar un while{}...
+
DECLARE num_libros INT;
  
 +
SELECT COUNT(*)
 +
INTO num_libros
 +
FROM libros;
  
 +
RETURN num_libros;
  
 +
END$$
  
−
<br />
+
DELIMITER ; </syntaxhighlight>
−
====Empregando PDO====
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="27" >
 
−
<?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');
+
==== Empregando PDO ====
  
−
try {
+
* Podemos chamar á función mediante un `SELECT`:
−
    $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);
 
  
−
?>
+
::<syntaxhighlight lang="php">
 +
$stmt = $conex->query(
 +
'SELECT CONSULTAR_NUM_LIBROS()'
 +
);
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
$numLibros = (int) $stmt->fetchColumn(); </syntaxhighlight>
−
    "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>Operacións BD</title>
 
−
    </head>
 
−
 
 
−
    <body>
 
−
    <?php       
 
−
    // Preparar a chamada ao procedemento almacenado
 
−
    $proc = $conex->prepare("CALL " . NOME_PROCEDEMENTO);
 
  
−
    // Se o procedemento necesita parámetros:
+
* `fetchColumn()` resulta especialmente útil cando unha consulta devolve un único valor.
−
    // $proc->execute(['param1' => $valor1, 'param2' => $valor2]);
 
−
    // Sen parámetros:
 
−
    $proc->execute();
 
−
       
 
−
    ?>       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
==== Funcións con parámetros ====
−
}    // Pecha o try do principio da páxina
 
−
catch (PDOException $e) {
 
−
  die("Erro PDO: " . $e->getMessage());
 
−
} finally {
 
−
  $conex = null; // Pechar a conexión
 
−
}
 
  
−
?>
+
* Supoñamos unha función:
−
</syntaxhighlight>
 
  
 +
::<syntaxhighlight lang="sql">
 +
SELECT Suma(5, 7) </syntaxhighlight>
  
 +
* Desde PDO:
  
−
<br />
+
::<syntaxhighlight lang="php">
−
==== Caso Práctico: Consulta chamando a procedementos almacenados ====
+
$stmt = $conex->prepare(
 +
'SELECT Suma(:a, :b)'
 +
);
  
−
* Vexamos un exemplo. Imos copiar o código da páxina '''Consultar_Libros.php''' e imos crear unha nova de nome '''Consultar_Libros_Proc''' co mesmo código, facendo as modificacións que se indican a continuación para que faga uso da chamada a un procedemento almacenado.
+
$stmt->bindValue(
 +
':a',
 +
5,
 +
PDO::PARAM_INT
 +
);
  
 +
$stmt->bindValue(
 +
':b',
 +
7,
 +
PDO::PARAM_INT
 +
);
  
−
* Creamos o procedemento almacenado:
+
$stmt->execute();
−
'''Creamos un procedemento almacenado dentro do MysqlWorkBench'''
+
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
$resultado = $stmt->fetchColumn(); </syntaxhighlight>
−
DELIMITER $$
+
 
 +
<br />
 +
 
 +
=== Paxinación de resultados ===
 +
 
 +
* En moitas ocasións unha consulta pode devolver unha cantidade elevada de rexistros.
 +
 
 +
: Para evitar amosalos todos simultaneamente podemos dividir o resultado en páxinas.
  
−
CREATE PROCEDURE `CONSULTAR_LIBROS` ()
+
* En MySQL podemos empregar `LIMIT` e `OFFSET`.
−
BEGIN
 
  
−
  select ìd,titulo
+
* Por exemplo:
−
  from libros
 
−
  order by titulo;
 
  
 +
::<syntaxhighlight lang="sql">
 +
SELECT id, titulo
 +
FROM libros
 +
ORDER BY titulo
 +
LIMIT 2 OFFSET 0; </syntaxhighlight>
  
−
END
+
: Devolverá os dous primeiros rexistros.
−
$$
 
−
</syntaxhighlight>
 
  
 +
::<syntaxhighlight lang="sql">
 +
SELECT id, titulo
 +
FROM libros
 +
ORDER BY titulo
 +
LIMIT 2 OFFSET 2; </syntaxhighlight>
  
 +
: Devolverá os dous seguintes.
  
−
* Definimos nunha constante o nome do procedemento almacenado dentro do arquivo bd.php.
+
==== Pasos necesarios para realizar a paxinación ====
  
−
:'''Arquivo: bd.php'''
+
* Necesitamos:
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('PROCEDEMENTO_CONSULTAR_LIBROS','CONSULTAR_LIBROS()');  // Se levara parámetros faríamos o mesmo que nos casos anteriores coas sentencias select, empregando %s, %d...
 
−
</syntaxhighlight>
 
  
 +
:# Coñecer o número total de rexistros.
 +
:# Establecer cantos rexistros se amosarán por páxina.
 +
:# Coñecer a páxina actual.
 +
:# Calcular o número total de páxinas.
 +
:# Calcular o desprazamento (`OFFSET`).
 +
:# Realizar a consulta correspondente.
  
  
 
<br />
 
<br />
−
=====Empregando procedementos almacenados=====
+
'''Número total de rexistros'''
  
−
* Debemos cambiar a chamada a 'query' pola chamada [http://php.net/manual/es/mysqli.real-query.php mysqli::real_query] ou [http://php.net/manual/es/mysqli.multi-query.php mysqli::multi_query]
+
::<syntaxhighlight lang="php">
−
:: Nos imos facer uso da chamada a '''multi_query''' xa que tamén a imos poder utilizar cando dentro dun mesmo procedemento almacenado teñamos varios select´s e queiramos recuperar o contido de todos eles (cada un será un conxunto de resultados).
+
$stmt = $conex->query(
 +
'SELECT COUNT(*)
 +
FROM libros'
 +
);
  
−
:'''Arquivo: consultar_libros_proc.php'''
+
$numLibros = (int) $stmt->fetchColumn(); </syntaxhighlight>
−
::<syntaxhighlight lang="php" enclose="div" highlight="24-26" >
 
−
<?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');
 
  
−
?>
+
<br />
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
'''Número de rexistros por páxina'''
−
    "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>Operacións BD</title>
 
−
    </head>
 
−
    <body>
 
−
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
 
−
        <?php       
 
−
        $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_LIBROS);
 
−
        if ($conex->multi_query($proc)){
 
−
            $result = $conex->store_result();
 
−
            if ($result && ($result->num_rows>0)){
 
−
                echo "<select name='lstLibros' onchange='this.form.submit()'>";
 
−
                echo "<option value='-1'></option>";
 
−
                while($row = $result->fetch_assoc()) {
 
−
                    if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){
 
−
                        $cadea="selected='selected'";
 
−
                    }
 
−
                    else{
 
−
                        $cadea='';
 
−
                    }
 
−
                    printf("<option value='%d' %s>%s</option>",$row["id"],$cadea,$row["titulo"]);
 
−
                }
 
−
                echo '</select>';
 
−
                $result->free();  // Liberamos da memoria os recursos
 
−
            }
 
−
            else {
 
−
                echo "Non hai datos que amosar...";
 
−
            }
 
−
               
 
−
        }
 
−
        else {
 
−
            echo "Houbo un erro na chamado ao procedemento:" . $conex->error; // A mensaxe do erro ($conex->error) non a teremos que amosar coa aplicación funcionando
 
−
        }
 
−
        ?>       
 
−
       
 
−
       
 
−
  </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();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
−
:* Liña 24: Creamos a cadea que vai chamar ao procedemento almacenado: CALL CONSULTAR_LIBROS()
+
::<syntaxhighlight lang="php">
−
:* Liña 25: Modificamos a liña e agora chamamos ao procedemento mysqli::multi_query(nome_proc). Dita chamada devolve false en caso de erro.
+
define(
−
:* Liña 26: Gardamos o resultado (o primeiro select do procedemento almacenado) no obxecto $result e xa operamos igual que no caso anterior cando usamos as sentencias 'select' directamente.
+
'CONST_NUM_LIBROS_PAXINA',
 +
10
 +
); </syntaxhighlight>
  
  
 +
<br />
 +
'''Obter e validar a páxina actual'''
  
−
<br />
+
* O número de páxina chegará normalmente mediante `GET`.
−
=====Empregando PDO=====
+
 
−
:'''Arquivo: consultar_libros_proc.php'''
+
::<syntaxhighlight lang="php">
−
::<syntaxhighlight lang="php" enclose="div" highlight="24-26" >
+
$paxinaActual = filter_input(
−
<?php
+
INPUT_GET,
−
/* Lembrar quitar isto ao probar o scripts sen erros */
+
'paxina',
−
ini_set('display_errors', 1);
+
FILTER_VALIDATE_INT
−
ini_set('display_startup_errors', 1);
+
);
−
error_reporting(E_ALL);
+
 
 +
if (
 +
$paxinaActual === false ||
 +
$paxinaActual === null ||
 +
$paxinaActual < 1
 +
) {
 +
 
 +
$paxinaActual = 1;
  
−
require ('/var/www/confBD/bd.php');
+
} </syntaxhighlight>
  
−
try {
+
* `htmlspecialchars()` non serve para validar que un dato sexa numérico.
−
    $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);
 
  
−
?>
+
: Neste caso empregamos `FILTER_VALIDATE_INT`.
  
−
<!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>Operacións BD</title>
 
−
    </head>
 
  
−
    <body>
+
<br />
−
    <?php       
+
'''Número total de páxinas'''
−
       
 
−
        $proc=$conex->prepare("CALL " . PROCEDEMENTO_CONSULTAR_LIBROS);
 
−
        $result = $proc->execute();
 
−
        // Comprobar se hai filas
 
−
        if ($result) {
 
−
            echo "<table border='1'>";
 
−
            while ($fila = $proc->fetch(PDO::FETCH_ASSOC)) {
 
−
                echo '<tr>';
 
−
                printf("<td>%s</td>",htmlentities($fila["titulo"]));
 
−
                echo '</tr>';
 
−
            }
 
−
            echo '</table>';
 
−
            $result = null; 
 
−
        } else {
 
−
          echo "Non hai datos que amosar!!!!";
 
−
        }
 
−
     
 
−
    ?>       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
::<syntaxhighlight lang="php">
−
}    // Pecha o try do principio da páxina
+
$totalPaxinas = (int) ceil(
−
catch (PDOException $e) {
+
$numLibros /
−
  die("Erro PDO: " . $e->getMessage());
+
CONST_NUM_LIBROS_PAXINA
−
} finally {
+
); </syntaxhighlight>
−
  $conex = null; // Pechar a conexión
 
−
}
 
  
−
?>
+
* Podemos evitar que o usuario solicite unha páxina superior á existente:
  
−
</syntaxhighlight>
+
::<syntaxhighlight lang="php">
 +
if (
 +
$totalPaxinas > 0 &&
 +
$paxinaActual > $totalPaxinas
 +
) {
  
−
=== Consulta chamando a procedementos almacenados con parámetros de entrada ===
+
$paxinaActual = $totalPaxinas;
  
−
* Podemos chamar a un procedemento almacenado enviando datos ao mesmo en forma de parámetros.
+
} </syntaxhighlight>
−
: Indicar que en case todos os xestores os parámetros poden ser de 3 tipos: '''de entrada''', '''de saída''' e de '''entrada/saída'''.
 
−
::* Os <u>parámetros de saída</u> (OUT) son variables que van poder ser modificadas no interior do procedemento e cuxo valor vai poder ser lido dentro do código da páxina php.
 
−
::* Os <u>parámetros de entrada</u> (IN) son variables que van poder ser utilizadas dentro do procedemento (chegan en forma de parámetros) pero as modificacións que se fagan no seu valor non chegan á paxina PHP.
 
−
::* Os <u>parámetros de entrada/entrada</u> (INOUT) son unha combinación dos dous anteriores.
 
  
−
: Máis información [http://www.mysqltutorial.org/stored-procedures-parameters.aspx neste enlace].
 
  
−
* Por defecto, se non especificamos nada, todos os parámetros dun procedemento almacenado son considerados 'de entrada'
+
<br />
 +
'''Calcular o OFFSET'''
  
−
* Primeiro teremos que definir o procedemento en Mysql:
+
::<syntaxhighlight lang="php">
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
$inicio = (
−
DELIMITER $$
+
$paxinaActual - 1
 +
) * CONST_NUM_LIBROS_PAXINA; </syntaxhighlight>
  
−
CREATE PROCEDURE `PROCEDEMENTO_CON_PARAMETROS`(IN _param1 varchar(100))
 
−
BEGIN
 
  
−
    SELECT col1,col2
 
−
    FROM TABOA_1
 
−
    WHERE col2=_param1;
 
  
−
END
+
<br />
−
</syntaxhighlight>  
+
'''Consulta paxinada'''
  
−
: Como vemos, xunto ao nome do procedemento hai que especificar o tipo (dentro dos tipos de mysql).
+
* Como `LIMIT` e `OFFSET` necesitan valores numéricos, indicaremos explicitamente o tipo mediante `PDO::PARAM_INT`.
−
: Unha vez definidos podemos utilizalos dentro do procedemento almacenado.
 
−
: Calquera modificación do valor do parámetro non se verá reflectidos no código PHP. Se no código PHP 'recuperamos' o valor do parámetro, este terá o mesmo valor que cando enviamos o dato ao procedemento.
 
  
 +
::<syntaxhighlight lang="php">
 +
$stmt = $conex->prepare(
 +
'SELECT id, titulo
 +
FROM libros
 +
ORDER BY titulo
 +
LIMIT :limite
 +
OFFSET :inicio'
 +
);
  
−
: Podemos definir máis parámetros, separando por comas cada un deles, por exemplo:
+
$stmt->bindValue(
−
::<syntaxhighlight lang="php" enclose="div" highlight="3,8,9" >
+
':limite',
−
DELIMITER $$
+
CONST_NUM_LIBROS_PAXINA,
−
 
+
PDO::PARAM_INT
−
CREATE PROCEDURE `PROCEDEMENTO_CON_PARAMETROS`(IN _param1 varchar(100),_param2 int)
+
);
−
BEGIN
 
  
−
    SELECT col1,col2
+
$stmt->bindValue(
−
    FROM TABOA_1
+
':inicio',
−
    WHERE col2=_param1 and
+
$inicio,
−
          col1=_param2
+
PDO::PARAM_INT
−
 
+
);
−
END
 
−
</syntaxhighlight>
 
  
 +
$stmt->execute();
  
−
* Unha  vez definido teremos que enviar os datos que queiramos dende PHP.
+
$libros = $stmt->fetchAll(); </syntaxhighlight>
  
  
 
<br />
 
<br />
−
====Orientado a obxectos====
+
'''Amosar os resultados'''
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS(%d,"%s")');
 
−
</syntaxhighlight>
 
−
: Fixarse como no caso de datos de tipo numérico non enviamos o dato con comiñas.
 
−
: Fixarse como 'xogamos' coas comiñas simples de dobres para pechar correctamente a cadea.
 
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="5,6" >
+
::<syntaxhighlight lang="php">
−
define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS(%d,"%s")');
+
if ($libros) {
  
−
        ........................
+
echo "<table border='1'>";
  
−
        $proc = sprintf(CHAMAR_PROCEDEMENTO_CON_PARAMETROS,valor_numérico,'valor_cadea');
+
foreach ($libros as $libro) {
−
        $proc = sprintf("CALL %s",$proc);
 
−
        if ($conex->multi_query($proc)){
 
−
            $result = $conex->store_result();
 
−
            if ($result && ($result->num_rows>0)){
 
−
                ................
 
−
            }
 
−
        }
 
  
−
</syntaxhighlight>  
+
    echo '<tr>';
  
−
: Fixarse que ao final o que temos que conseguir é esta cadea: CALL PROCEDEMENTO_CON_PARAMETROS(5,'valor')  // 5 e valor son datos de exemplo
+
    printf(
−
: Isto o podemos conseguir de diferentes formas. Eu vos amoso unha delas.
+
        '<td>%s</td>',
−
 
+
        htmlspecialchars(
−
 
+
            $libro['titulo'],
−
 
+
            ENT_QUOTES | ENT_SUBSTITUTE,
−
<br />
+
            'UTF-8'
−
====Empregando PDO====
+
        )
 +
    );
  
−
: En PDO, a preparación da chamada se fai coma sempre:
+
    echo '</tr>';
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
}
−
$proc = $conex->prepare("CALL NOME_PROCEDEMENTO(:param_num, :param_text)");
 
−
</syntaxhighlight>
 
  
−
: Neste exemplo estou a supoñer que o procedemento leva dous parámetros de entrada, un de tipo numérico e outro de tipo cadea.
+
echo '</table>';
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="6,7" >
+
} else {
−
// Valores dos parámetros
 
−
    $valor_num = 42;
 
−
    $valor_text = "exemplo";
 
−
 
 
−
    // Asignamos valores e tipos
 
−
    $proc->bindValue(':param_num', $valor_num, PDO::PARAM_INT);
 
−
    $proc->bindValue(':param_text', $valor_text, PDO::PARAM_STR);
 
  
−
    // Executamos o procedemento
+
echo 'Non hai datos que amosar.';
−
    $proc->execute();
 
−
    while ($fila = $proc->fetch(PDO::FETCH_ASSOC)) {
 
−
      .............
 
−
    }
 
−
</syntaxhighlight>
 
−
: Emprego bindValue para pasarelle o valor xunto có tipo de dato.
 
−
: Se queremos unha forma máis sinxela podemos facelo como ata o de agora e enviar os valores no execute pero teríamos que <u>estar seguros de que estamos a enviar o dato có tipo correcto</u>.
 
  
 +
} </syntaxhighlight>
  
  
  
 
<br />
 
<br />
−
==== Caso Práctico: Consulta chamando a procedementos almacenados con parámetros de entrada ====
+
'''Amosar os enlaces ás páxinas'''
  
−
* Seguindo có noso exemplo...imos crear unha nova páxina de nome '''buscar_libros_por_autor_proc.php''' que faga uso de procedementos almacenados en vez de sentencias SQL directamente no código.
+
::<syntaxhighlight lang="php">
 +
echo '<div>';
  
−
'''Solución:'''
+
for (
−
* '''Procedemento almacenado CONSULTAR_LIBROS_POR_AUTOR en Mysql'''
+
$cont = 1;
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
$cont <= $totalPaxinas;
−
DELIMITER $$
+
$cont++
 +
) {
  
−
CREATE PROCEDURE `CONSULTAR_LIBROS_POR_AUTOR`(IN p_autor_buscar varchar(100))
+
if ($cont === $paxinaActual) {
−
BEGIN
 
  
−
select CONCAT(apelidos,',',nome) as nome_autor,libros.id as id_libro,titulo from libros
+
    printf(
−
            INNER JOIN libro_autor ON (libro_autor.libro_id=libros.id)
+
        '<strong>%d</strong> ',
−
            INNER JOIN autores ON (libro_autor.autor_id=autores.id)
+
        $cont
−
where CONCAT(autores.nome,' ',autores.apelidos) like  CONCAT('%', p_autor_buscar , '%')
+
    );
−
order by nome_autor,titulo;
 
−
 
 
−
END$$
 
−
</syntaxhighlight>
 
−
 
 
−
: <u>Nota:</u> Para comprobar se funciona facede primeiro unha chamada dende o propio Mysql coa orde: call CONSULTAR_LIBROS_POR_AUTOR('dru');
 
−
: Fixarse como o parámetro é de tipo IN (valor por defecto, podería non poñerse nada).
 
  
 +
} else {
 +
 +
    printf(
 +
        "<a href='?paxina=%d'>%d</a> ",
 +
        $cont,
 +
        $cont
 +
    );
 +
}
 +
 +
}
 +
 +
echo '</div>'; </syntaxhighlight>
 +
 +
 +
 +
==== Exemplo completo de paxinación ====
  
  
−
<br />
 
−
=====Orientado a obxectos=====
 
−
* Definimos a constante en PHP no que está definido o nome do procedemento e o parámetro que vai levar.
 
−
:'''Arquivo: bd.php'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('PROCEDEMENTO_CONSULTAR_LIBROS_POR_AUTOR','CONSULTAR_LIBROS_POR_AUTOR("%s")');
 
−
</syntaxhighlight>
 
  
−
:<u>Nota:</u> Fixarse como o parámetro ao ser de tipo 'cadea' debe ir entre comillas.
+
'''Aquivo bd.php'''
 +
::<syntaxhighlight lang="php">
 +
define(
 +
    'CONST_NUM_LIBROS_PAXINA',
 +
    10
 +
);
 +
::</syntaxhighlight>
  
  
 +
'''Páxina php a cargar'''
 +
::<syntaxhighlight lang="php">
  
−
* '''Arquivo buscar_libros_por_autor_proc.php'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="44-47" >
 
 
<?php
 
<?php
−
/* Lembrar quitar isto ao probar o scripts sen erros */
+
 
−
ini_set('display_errors', 1);
+
ini_set('display_errors', '1');
−
ini_set('display_startup_errors', 1);
 
 
error_reporting(E_ALL);
 
error_reporting(E_ALL);
  
−
require ('/var/www/confBD/bd.php');
+
require '/var/www/confBD/bd.php';
 +
 
  
 
try {
 
try {
−
    $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD); 
 
−
    $conex->set_charset('utf8mb4');
 
  
−
     $erro="";   // Variable que a usaremos para amosar os posibles erros que poidan darse
+
     $dsn = sprintf(
 +
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
 +
        SERVIDOR,
 +
        BD
 +
    );
 +
 
 +
    $conex = new PDO(
 +
        $dsn,
 +
        USUARIO,
 +
        PASSWORD,
 +
        [
 +
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
 +
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
 +
        ]
 +
    );
 +
 
 +
 
 +
    /*
 +
    * Número total de libros.
 +
    */
 +
    $stmt = $conex->query(
 +
        'SELECT COUNT(*)
 +
        FROM libros'
 +
    );
  
−
?>
+
    $numLibros = (int) $stmt->fetchColumn();
  
−
<!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>Operacións BD</title>
 
−
        <script type="text/javascript">
 
−
            function abrirVentana($url){
 
−
                window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
 
−
            }
 
−
        </script>
 
−
       
 
−
    </head>
 
  
−
     <body>
+
     /*
−
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
+
    * Número total de páxinas.
−
            Introduce o nome do autor a buscar:
+
    */
−
            <input type='text' size='50' maxlength="100" required name="txtAutor" value='<?php echo (isset($_POST['txtAutor']) ? $_POST["txtAutor"]:""); ?>'/>
+
    $totalPaxinas = (int) ceil(
−
            <input type="submit" value="BUSCAR" />
+
        $numLibros /
−
            <input type='button' onclick="javascript:this.form['txtAutor'].value='';" value="LIMPAR" />
+
         CONST_NUM_LIBROS_PAXINA
−
        <?php       
+
    );
−
        if (!empty($_POST['txtAutor'])){  // Premimos o botón de busca
 
−
            $textoBuscar = filter_var($_POST['txtAutor'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); // Eliminamos da cadea de busca todos os caracteres non permitidos
 
−
            if (strlen($textoBuscar)<3){  // É un exemplo de control de erros. Indico que o texto a buscar ten que ter a lo menos 3 caracteres
 
−
                $erro="<h2>A cadea a buscar non pode ser menor de 3 caracteres</h2>";
 
−
            }
 
−
            else {
 
−
                $proc = sprintf(PROCEDEMENTO_CONSULTAR_LIBROS_POR_AUTOR,$conex->real_escape_string($textoBuscar)); // Cambiamos o texto a buscar polo parámetro definido na constante
 
−
                $proc = sprintf("CALL %s",$proc);
 
−
                if ($conex->multi_query($proc)){
 
−
                    $result = $conex->store_result();
 
−
                    if ($result->num_rows>0){
 
−
                        $autor='';  // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                        $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                        echo "<table border='1'>";
 
−
                        echo "<th>Autor</th><th>Libro</th>";
 
−
                        while($row = $result->fetch_assoc()) {
 
−
                            if ($last_autor==$row["nome_autor"]){
 
−
                                $autor='';
 
−
                            }
 
−
                            else {
 
−
                                $autor=$row["nome_autor"];
 
−
                            }
 
−
                            echo '<tr>';
 
−
                            $onclick=sprintf("javascript:abrirVentana(\"detalle_libro.php?libro=%d\");",htmlentities($row["id_libro"]));
 
−
                            printf("<td>%s</td><td><a href='#' onclick='%s'>%s</a></td>",$autor,$onclick,htmlentities($row['titulo']));
 
−
                            echo '</tr>';
 
−
                            $last_autor=$row["nome_autor"];
 
−
                        }
 
−
                        $result->free();
 
−
                        echo "</table>";
 
−
                    }
 
−
                    else {
 
−
                        $erro = "Non hai resultados,";
 
−
                    }
 
−
                }
 
−
            }
 
−
         }
 
−
       
 
−
        ?>       
 
−
        </form>
 
−
        <?php       
 
−
        if ($erro!='') {
 
−
            printf("<h3>%s</h3>",$erro);
 
−
        }
 
−
        ?>       
 
−
       
 
−
       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
 
−
}    // Pecha o try do principio 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();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
  
−
:* Liñas 44 e 45: Creamos a cadea que vai chamar ao procedemento almacenado de Mysql: CALL CONSULTAR_LIBROS_POR_AUTOR('dru'); (é un exemplo de busca pola palabra dru)
+
    /*
 +
    * Páxina solicitada polo usuario.
 +
    */
 +
    $paxinaActual = filter_input(
 +
        INPUT_GET,
 +
        'paxina',
 +
        FILTER_VALIDATE_INT
 +
    );
  
 +
    if (
 +
        $paxinaActual === false ||
 +
        $paxinaActual === null ||
 +
        $paxinaActual < 1
 +
    ) {
  
 +
        $paxinaActual = 1;
 +
    }
  
  
 +
    if (
 +
        $totalPaxinas > 0 &&
 +
        $paxinaActual > $totalPaxinas
 +
    ) {
  
−
<br />
+
        $paxinaActual = $totalPaxinas;
−
=====Empregando PDO=====
+
    }
−
* Definimos a constante en PHP no que está definido o nome do procedemento e o parámetro que vai levar.
 
−
:'''Arquivo: bd.php'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('PROCEDEMENTO_CONSULTAR_LIBROS_POR_AUTOR','CONSULTAR_LIBROS_POR_AUTOR(:nome)');
 
−
</syntaxhighlight>
 
  
  
 +
    /*
 +
    * Primeiro rexistro da páxina.
 +
    */
 +
    $inicio = (
 +
        $paxinaActual - 1
 +
    ) * CONST_NUM_LIBROS_PAXINA;
  
−
* '''Arquivo buscar_libros_por_autor_proc.php'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="44-47" >
 
−
<?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');
+
    /*
 +
    * Consulta dos libros da páxina actual.
 +
    */
 +
    $stmt = $conex->prepare(
 +
        'SELECT id, titulo
 +
        FROM libros
 +
        ORDER BY titulo
 +
        LIMIT :limite
 +
        OFFSET :inicio'
 +
    );
 +
 
 +
    $stmt->bindValue(
 +
        ':limite',
 +
        CONST_NUM_LIBROS_PAXINA,
 +
        PDO::PARAM_INT
 +
    );
  
−
try {
+
     $stmt->bindValue(
−
     $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8mb4",SERVIDOR,BD);
+
        ':inicio',
−
    $conex = new PDO($dsn, USUARIO, PASSWORD);
+
        $inicio,
−
    $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
        PDO::PARAM_INT
 +
    );
  
−
     $erro="";   // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
+
     $stmt->execute();
−
?>
 
  
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+
     $libros = $stmt->fetchAll();
−
     "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>Operacións BD</title>
 
−
        <script type="text/javascript">
 
−
            function abrirVentana($url){
 
−
                window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
 
−
            }
 
−
        </script>
 
−
       
 
−
    </head>
 
  
−
    <body>
+
} catch (PDOException $e) {
−
        <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
 
−
            Introduce o nome do autor a buscar:
 
−
            <input type='text' size='50' maxlength="100" required name="txtAutor" value='<?php echo (isset($_POST['txtAutor']) ? $_POST["txtAutor"]:""); ?>'/>
 
−
            <input type="submit" value="BUSCAR" />
 
−
            <input type='button' onclick="javascript:this.form['txtAutor'].value='';" value="LIMPAR" />
 
−
        <?php       
 
−
        if (!empty($_POST['txtAutor'])){ // Premimos o botón de busca
 
−
            $textoBuscar = filter_var($_POST['txtAutor'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); // Eliminamos da cadea de busca todos os caracteres non permitidos
 
−
            if (strlen($textoBuscar)<3){  // É un exemplo de control de erros. Indico que o texto a buscar ten que ter a lo menos 3 caracteres
 
−
                $erro="<h2>A cadea a buscar non pode ser menor de 3 caracteres</h2>";
 
−
            }
 
−
            else {
 
−
                $proc = $conex->prepare("CALL " . PROCEDEMENTO_CONSULTAR_LIBROS_POR_AUTOR);
 
−
                $proc->bindValue(':autor',$_POST['txtAutor'],  PDO::PARAM_STR);
 
−
                $result = $proc->execute();
 
  
−
                if ($result) {
+
    error_log($e->getMessage());
−
                        $autor='';  // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
+
    die('Erro relacionado coa base de datos.');
−
                        $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
 
−
                        echo "<table border='1'>";
 
−
                        echo "<th>Autor</th><th>Libro</th>";
 
−
                        while ($row = $proc->fetch(PDO::FETCH_ASSOC)) {
 
−
                            if ($last_autor==$row["nome_autor"]){
 
−
                                $autor='';
 
−
                            }
 
−
                            else {
 
−
                                $autor=$row["nome_autor"];
 
−
                            }
 
−
                            echo '<tr>';
 
−
                            $onclick=sprintf("javascript:abrirVentana(\"detalle_libro.php?libro=%d\");",htmlentities($row["id_libro"]));
 
−
                            printf("<td>%s</td><td><a href='#' onclick='%s'>%s</a></td>",$autor,$onclick,htmlentities($row['titulo']));
 
−
                            echo '</tr>';
 
−
                            $last_autor=$row["nome_autor"];
 
−
                        }
 
−
                        $result = null;
 
−
                        echo "</table>";
 
−
                    }
 
−
                    else {
 
−
                        $erro = "Non hai resultados,";
 
−
                    }
 
−
                }
 
−
            }
 
−
       
 
−
        ?>       
 
−
        </form>
 
−
        <?php       
 
−
        if ($erro!='') {
 
−
            printf("<h3>%s</h3>",$erro);
 
−
        }
 
−
        ?>       
 
−
       
 
−
       
 
−
    </body>   
 
−
</html>
 
  
−
<?php
+
} catch (Throwable $e) {
−
}   // 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>
 
  
−
:* Liñas 44 e 45: Creamos a cadea que vai chamar ao procedemento almacenado de Mysql: CALL CONSULTAR_LIBROS_POR_AUTOR('dru'); (é un exemplo de busca pola palabra dru)
+
    die('Produciuse un erro na aplicación.');
  
 +
}
  
 +
?>
  
 +
<!DOCTYPE html>
  
−
<br />
+
<html lang="gl">
  
−
=== Consulta chamando a procedementos almacenados con parámetros de saída ===
+
<head>
 +
    <meta charset="UTF-8">
 +
    <title>Paxinación de libros</title>
 +
</head>
  
−
* Agora imos aplicar o concepto de parámetros de saída, que como indicamos no punto anterior, son parámetros que van poder ser modificados o seu valor dentro do procedemento almacenado e recoller o seu valor modificado dende a parte de PHP.
+
<body>
  
−
* Primeiro teremos que definir o procedemento en Mysql:
+
<?php if ($libros): ?>
−
::<syntaxhighlight lang="php" enclose="div" highlight="3,7" >
 
−
DELIMITER $$
 
  
−
CREATE PROCEDURE `PROCEDEMENTO_CON_PARAMETROS`(OUT _param1 varchar(100))
+
<table border="1">
−
BEGIN
 
  
−
     SELECT col2
+
     <?php foreach ($libros as $libro): ?>
−
    INTO _param1
 
−
    FROM TABOA_1
 
−
    WHERE col1=1;
 
  
−
END
+
        <tr>
−
</syntaxhighlight>  
+
            <td>
 +
                <?= htmlspecialchars(
 +
                    $libro['titulo'],
 +
                    ENT_QUOTES | ENT_SUBSTITUTE,
 +
                    'UTF-8'
 +
                ) ?>
 +
            </td>
 +
        </tr>
  
−
: Agora especificamos que o parámetro é de saída antepoñendo a palabra OUT ao seu nomo.
+
    <?php endforeach; ?>
−
: Neste exemplo estamos a supoñer que a consulta devolve unha única fila e estamos gardando o resultado da consulta no parámetro de saída.
 
−
: Podemos modificar o seu valor dentro do procedemento coa orde: SET _param1=valor;
 
  
 +
</table>
  
 +
<?php else: ?>
  
−
<br />
+
<p>Non hai libros que amosar.</p>
−
====Orientado a obxectos====
 
  
 +
<?php endif; ?>
  
−
* Unha  vez definido teremos que construír a chamada ao procedemento enviando o nome dunha variable (para recoller o valor modificado).
+
<?php if ($totalPaxinas > 1): ?>
−
: O nome da variable terá este formato: '''@'''nomeVariable:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS("@nomeVariable")');
 
−
</syntaxhighlight>  
 
−
: Fixarse como a variable é de tipo cadea e vai entre comiñas.
 
  
 +
<div>
  
−
* Agora a chamada se fai igual que no caso anterior, pero fixarse que neste caso O PROCEDEMENTO NON DEVOLVE UN CONXUNTO DE RESULTADOS. Podería ser e nese caso obraríamos igual que no caso anterior, a maiores do que imos ver agora.
+
    <?php
−
: Para recoller o dato da variable teremos que facer unha consulta SELECT da forma: SELECT @nomeVariable as valorDevolto
+
    for (
 +
        $cont = 1;
 +
        $cont <= $totalPaxinas;
 +
        $cont++
 +
    ):
 +
    ?>
  
−
::<syntaxhighlight lang="php" enclose="div" highlight="10,14" >
+
        <?php if ($cont === $paxinaActual): ?>
−
<?php
 
−
        define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS("@nomeVariable")');
 
  
−
        ........................
+
            <strong>
 +
                <?= $cont ?>
 +
            </strong>
  
−
         $proc = sprintf("CALL %s",CHAMAR_PROCEDEMENTO_CON_PARAMETROS);
+
         <?php else: ?>
−
        if ($conex->multi_query($proc)){
 
−
            $result = $conex->store_result();
 
  
−
             if (!($resultado = $conex->query("SELECT @nomeVariable as valorDevolto"))) {
+
             <a href="?paxina=<?= $cont ?>">
−
                echo "Fallou a obtención: (" . $conex->errno . ") " . $conex->error;
+
                <?= $cont ?>
−
            }
+
             </a>
−
            else {
 
−
                $fila = $resultado->fetch_assoc();
 
−
                echo $fila['valorDevolto']);
 
−
             }
 
  
−
         }
+
         <?php endif; ?>
−
        ........................
 
  
 +
    <?php endfor; ?>
  
−
?>
+
</div>
  
−
</syntaxhighlight>  
+
<?php endif; ?>
  
−
: Fixarse que ao final o que temos que conseguir é esta cadea: CALL PROCEDEMENTO_CON_PARAMETROS(5,'valor')  // 5 e valor son datos de exemplo
+
</body>
−
: Isto o podemos conseguir de diferentes formas. Eu vos amoso unha delas.
+
</html>
 +
</syntaxhighlight>
  
 +
==== Sobre a optimización da paxinación ====
  
 +
* En xeral non debemos enviar pola URL o número total de rexistros e confiar posteriormente nese dato:
  
−
: <u>Nota:</u> Non fai falla facer un alias do nome da variable. Podemos empregar o propio nome no fetch_assoc().
+
::<syntaxhighlight lang="text">
−
:: Podemos poñer:
+
?paxina=2&numtotal=50 </syntaxhighlight>
  
−
:::<syntaxhighlight lang="php" enclose="div" highlight="5,10" >
+
: Os datos enviados mediante `GET` poden ser modificados polo usuario.
−
        $proc = sprintf("CALL %s",CHAMAR_PROCEDEMENTO_CON_PARAMETROS);
 
−
        if ($conex->multi_query($proc)){
 
−
            $result = $conex->store_result();
 
  
−
            if (!($resultado = $conex->query("SELECT @nomeVariable"))) {
+
* Gardar o número total de rexistros nunha variable de sesión tampouco garante que o dato siga sendo correcto, xa que outros usuarios poderían inserir ou eliminar rexistros.
−
                echo "Fallou a obtención: (" . $conex->errno . ") " . $conex->error;
 
−
            }
 
−
            else {
 
−
                $fila = $resultado->fetch_assoc();
 
−
                echo $fila['@nomVariable']);
 
−
            }
 
  
−
        }
+
* Como primeira solución realizaremos en cada petición:
−
</syntaxhighlight>
 
  
 +
::<syntaxhighlight lang="sql">
 +
SELECT COUNT(*)
 +
FROM libros </syntaxhighlight>
  
 +
* Se nunha aplicación real esta operación chegase a ser un problema de rendemento poderían estudarse técnicas de caché ou outras estratexias de optimización.
  
 
<br />
 
<br />
−
====Empregando PDO====
 
  
 +
=== Exercicio proposto ===
 +
 +
* Crea unha páxina chamada `buscar_libros.php` que permita buscar libros empregando os seguintes criterios:
  
−
* Unha  vez definido teremos que construír a chamada ao procedemento enviando o nome dunha variable (para recoller o valor modificado).
+
:* '''Título''': unha caixa de texto que realizará unha busca mediante `LIKE`.
−
: O nome da variable terá este formato: '''@'''nomeVariable:
+
:* '''Prezo''': un campo no que se validará que o valor introducido sexa numérico.
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
+
:* '''Estado''': unha lista `<select>` cos estados dispoñibles.
−
define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS(@nomeVariable)');
+
:* '''Autor''': unha lista `<select>` cos autores dispoñibles.
−
</syntaxhighlight>
+
:* '''Criterio de ordenación''': título, prezo ou autor.
  
 +
* O usuario poderá combinar os diferentes criterios.
  
−
* Agora a chamada se fai igual que no caso anterior, pero fixarse que neste caso O PROCEDEMENTO NON DEVOLVE UN CONXUNTO DE RESULTADOS. Podería ser e nese caso obraríamos igual que no caso anterior, a maiores do que imos ver agora.
+
* Os resultados deberán poder amosarse paxinados.
−
: Para recoller o dato da variable teremos que facer unha consulta SELECT da forma: SELECT @nomeVariable as valorDevolto
 
−
 
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="10,14" >
 
−
<?php
 
−
        define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS(@nomeVariable)');
 
−
 
 
−
        ........................
 
−
 
 
−
        $stmt = $conex->prepare("CALL " . CHAMAR_PROCEDEMENTO_CON_PARAMETROS);
 
−
        $stmt->execute();
 
−
 
 
−
        $res = $conex->query("SELECT @nomeVariable AS total")->fetch(PDO::FETCH_ASSOC);
 
−
        if ($res){
 
−
          echo $res['valorDevolto'];
 
−
        }
 
−
        ........................
 
−
 
 
−
 
 
−
?>
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
−
: Fixarse que ao final o que temos que conseguir é esta cadea: CALL PROCEDEMENTO_CON_PARAMETROS(5,'valor')  // 5 e valor son datos de exemplo
 
−
: Isto o podemos conseguir de diferentes formas. Eu vos amoso unha delas.
 
−
 
 
−
 
 
−
</syntaxhighlight>
 
−
 
 
  
 +
* A solución deberá:
  
 +
:* Empregar PDO.
 +
:* Empregar sentenzas preparadas para todos os datos externos.
 +
:* Validar os valores recibidos mediante `GET` ou `POST`.
 +
:* Empregar `bindValue()` co tipo correspondente cando sexa necesario.
 +
:* Non concatenar directamente datos introducidos polo usuario dentro dunha sentenza SQL.
 +
:* Empregar `htmlspecialchars()` cando se amosen cadeas dentro do HTML.
 +
:* Capturar os erros PDO mediante `PDOException`.
 +
:* Rexistrar os detalles do erro mediante `error_log()`.
 +
:* Amosar ao usuario unha mensaxe xenérica en caso de erro.
 +
:* Capturar outros erros da aplicación mediante `Throwable`.
  
 
<br />
 
<br />
  
−
==== Caso práctico: Consulta chamando a procedementos almacenados con parámetros de saída ====
+
== Resumo das funcións PDO empregadas ==
  
−
* Segundo o noso exemplo, imos crear un procedemento almacenado que devolva nun parámetro de saída o número de libros que temos na nosa tenda.
+
::<syntaxhighlight lang="php">
−
 
+
! Método / constante
−
 
 
−
* '''Procedemento almacenado CONSULTAR_NUM_LIBROS en Mysql'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
DELIMITER $$
 
−
 
 
−
CREATE DEFINER=`root`@`localhost` PROCEDURE `CONSULTAR_NUM_LIBROS`(OUT p_num_libros int)
 
−
BEGIN
 
−
SELECT COUNT(*)
 
−
INTO p_num_libros
 
−
FROM libros;
 
−
 
 
−
END
 
−
$$
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
: Como se pode ver leva un parámetro de saída (OUT). Indicar que podemos poñer calquera combinación de parámetros na definición dun procedemento almacenado.
 
−
 
 
−
 
 
−
<br />
 
−
=====Orientado a obxectos=====
 
−
* '''Arquivo bd.php''':
 
−
* Definimos a chamada ao procedemento cunha variable no seu interior que vai ser a que garde o valor modificado de dentro do procedemento.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('PROCEDEMENTO_CONSULTAR_NUM_LIBROS','CONSULTAR_NUM_LIBROS(@numero_libros)');
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
* '''Arquivo consultar_num_libros.php'''
 
−
: Esta páxina está a modo de exemplo. Loxicamente isto podería ser utilizado dentro dunha páxina para obter o número de libros sen necesidade de crear unha páxina específica.
 
−
::<syntaxhighlight lang="php" 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 {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); 
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
$erro="";  // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
 
−
?>
 
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
 
−
    <head>
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Operacións BD</title>
 
−
       
 
−
    </head>
 
−
 
 
−
    <body>
 
−
 
 
−
        <?php
 
−
            $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
 
−
            if ($conex->multi_query($proc)){
 
−
                $result = $conex->store_result();
 
−
                if (!($resultado = $conex->query("SELECT @numero_libros as _p_out"))) {
 
−
                    echo "Fallou a obtención: (" . $conex->errno . ") " . $conex->error;
 
−
                }
 
−
                else {
 
−
                    $fila = $resultado->fetch_assoc();
 
−
                    printf("<h1>NUMERO DE LIBROS A VENDA:%d",$fila['_p_out']);
 
−
                }
 
−
            }
 
−
        ?>       
 
−
       
 
−
       
 
−
    </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();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
 
 
−
=====Empregando PDO=====
 
−
* '''Arquivo bd.php''':
 
−
* Definimos a chamada ao procedemento cunha variable no seu interior que vai ser a que garde o valor modificado de dentro do procedemento.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('PROCEDEMENTO_CONSULTAR_NUM_LIBROS','CONSULTAR_NUM_LIBROS(@numero_libros)');
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
* '''Arquivo consultar_num_libros.php'''
 
−
: Esta páxina está a modo de exemplo. Loxicamente isto podería ser utilizado dentro dunha páxina para obter o número de libros sen necesidade de crear unha páxina específica.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
<?php
 
−
/* Lembrar quitar isto ao probar o scripts sen erros */
 
−
ini_set('display_errors', 1);
 
−
ini_set('display_startup_errors', 1);
 
−
error_reporting(E_ALL);
 
−
 
 
−
require ('/var/www/confBD/bd.php');
 
−
 
 
−
try {
 
−
    $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8mb4",SERVIDOR,BD);
 
−
    $conex = new PDO($dsn, USUARIO, PASSWORD);
 
−
    $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
−
 
 
−
    $erro="";  // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
 
−
?>
 
−
 
 
−
 
 
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
 
−
    <head>
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Operacións BD</title>
 
−
       
 
−
    </head>
 
−
 
 
−
    <body>
 
−
 
 
−
        <?php
 
−
            $stmt = $conex->prepare("CALL " . PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
 
−
            $stmt->execute();
 
−
       
 
−
            $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
 
−
            $res = $conex->query("SELECT @numero_libros AS valorDevolto")->fetch(PDO::FETCH_ASSOC);
 
−
            if ($res){
 
−
                printf("<h1>NUMERO DE LIBROS A VENDA:%d",$res['valorDevolto']);
 
−
            }
 
−
        ?>       
 
−
       
 
−
       
 
−
    </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>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
 
 
−
=== Consulta chamando a funcións ===
 
−
 
 
−
* Dende Mysql pódense definir funcións, que son procedementos almacenados que devolven un valor dun tipo de datos.
 
−
 
 
−
: [http://dev.mysql.com/doc/refman/5.7/en/create-procedure.html Definicións de funcións dende Mysql].
 
−
 
 
−
 
 
−
* Vexamos un exemplo:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
DELIMITER $$
 
−
 
 
−
CREATE FUNCTION `EXEMPLO_FUNCION`() RETURNS int
 
−
BEGIN
 
−
    declare variable int;
 
−
 
 
−
SELECT col2
 
−
INTO variable
 
−
FROM TABLA_1
 
−
        WHERE col1=1;
 
−
 
 
−
return variable;
 
−
 
 
−
 
 
−
END
 
−
$$
 
−
</syntaxhighlight>
 
−
 
 
−
: Como vemos, esta función vai devolver un número de tipo integer.
 
−
 
 
−
 
 
−
* Para chamar á función dende Mysql teremos que 'engadila' dentro dunha cláusula SELECT.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('FUNCION_EXEMPLO_MYSQL','select EXEMPLO_FUNCION() as valor_devolto');
 
−
</syntaxhighlight>
 
−
 
 
−
: Como vemos, a tratamos coma dunha cláusula SELECT calquera, dándolle un nome (alias) ao resultado da execución da función.
 
−
 
 
−
 
 
−
<br />
 
−
====Orientado a obxectos====
 
−
* Agora soamente queda obter o resultado como fixemos [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Consulta no exemplo de consulta], anteriormente visto.
 
−
 
 
−
 
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="19" >
 
−
 
 
−
<?php
 
−
 
 
−
define('FUNCION_EXEMPLO_MYSQL','select EXEMPLO_FUNCION() as valor_devolto');
 
−
 
 
−
$conex = new mysqli('SERVIDOR','USUARIO','PASSWORD','BD');
 
−
$conex->set_charset('utf8');
 
−
 
 
−
if ($conex->connect_error){
 
−
    die('Erro de Conexi&oacute;n (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
 
−
}
 
−
 
 
−
?>
 
−
 
 
−
 
 
−
        <?php
 
−
            $result=$conex->query(FUNCION_EXEMPLO_MYSQL);
 
−
            if ($result || ($result->num_rows>0)){
 
−
                $row = $result->fetch_assoc();
 
−
                printf("<h1>Dato devolto:%d</h1>",$row['valor_devolto']);
 
−
                $result->free();
 
−
            }
 
−
            else {
 
−
                echo "Fallou a chamada á función: (" . $conex->errno . ") " . $conex->error;
 
−
            }
 
−
 
 
−
       
 
−
            $conex->close();  // Pechamos a conexión
 
−
       
 
−
        ?>       
 
−
</syntaxhighlight>
 
−
 
 
−
:<u>Nota:</u> Fixarse como na liña 19 a referencia que utilizamos é o alias posto no DEFINE.
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
====Empregando PDO====
 
−
 
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="19,26" >
 
−
<?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);
 
−
 
 
−
try {
 
−
    $dsn = 'mysql:host=localhost;dbname=php;charset=utf8mb4';
 
−
    $usuario = 'user_php';
 
−
    $password = 'user_php';
 
−
 
 
−
    $conex = new PDO($dsn, $usuario, $password);
 
−
    $conex->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 
−
 
 
−
?>
 
−
 
 
−
 
 
−
<?php
 
−
    define('FUNCION_EXEMPLO_MYSQL','select EXEMPLO_FUNCION() as valor_devolto');
 
−
 
 
−
    $stmt = $conex->prepare(FUNCION_EXEMPLO_MYSQL);
 
−
 
 
−
    // Executar
 
−
    $stmt->execute();
 
−
    $res = $stmt->fetch(PDO::FETCH_ASSOC);
 
−
    echo "O resultado da función é: " . $res['valor_devolto'];
 
−
    $stmt = null;
 
−
 
 
−
} catch (PDOException $e) {
 
−
    die("Erro PDO: " . $e->getMessage());
 
−
} finally {
 
−
    $conex = null;
 
−
}
 
−
 
 
−
?>       
 
−
</syntaxhighlight>
 
−
 
 
−
:<u>Nota:</u> Fixarse como na liña 26 a referencia que utilizamos é o alias posto no DEFINE na liña 19.
 
−
 
 
−
 
 
−
* Se a función levara parámetros teríamos que pasalos como antes nos procedementos almacenados:
 
−
 
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
// Chamamos á función usando SELECT
 
−
    $stmt = $conex->prepare("SELECT Suma(:a, :b) AS resultado");
 
−
 
 
−
    // Parametros
 
−
    $stmt->bindValue(':a', 5, PDO::PARAM_INT);
 
−
    $stmt->bindValue(':b', 7, PDO::PARAM_INT);
 
−
 
 
−
    // Executar
 
−
    $stmt->execute();
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
==== Caso Práctico: Consulta chamando a funcións ====
 
−
 
 
−
* Imos facer un exemplo aplicado ao noso proxecto.
 
−
: Imos facer que o número de libros da nosa tenda sexa obtido utilizando unha función e non o procedemento almacenado con parámetro de saída da sección anterior.
 
−
 
 
−
* '''Creamos a función en Mysql'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
DELIMITER $$
 
−
 
 
−
CREATE FUNCTION `CONSULTAR_NUM_LIBROS`() RETURNS int READS SQL DATA
 
−
BEGIN
 
−
    declare num_libros int;
 
−
 
 
−
SELECT COUNT(*)
 
−
INTO num_libros
 
−
FROM libros;
 
−
 
 
−
return num_libros;
 
−
END$$
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
=====Orientado a obxectos=====
 
−
* '''Aqruivo bd.php'''
 
−
: Definimos a constante que vai chamar á función.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('FUNCION_CONSULTAR_NUM_LIBROS','select CONSULTAR_NUM_LIBROS() as num_libros');
 
−
</syntaxhighlight>
 
−
 
 
−
: Fixarse no alias da chamada á función.
 
−
 
 
−
 
 
−
* '''Creamos unha páxina consultar_num_libros_con_funcionMYSQL.php'''
 
−
::<syntaxhighlight lang="php" 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 {
 
−
    $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); 
 
−
    $conex->set_charset('utf8mb4');
 
−
 
 
−
$erro="";  // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
 
−
?>
 
−
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
−
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
−
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
 
−
    <head>
 
−
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 
−
        <title>Operacións BD</title>
 
−
       
 
−
    </head>
 
−
 
 
−
    <body>
 
−
 
 
−
        <?php
 
−
        $result=$conex->query(FUNCION_CONSULTAR_NUM_LIBROS);
 
−
        if ($result && ($result->num_rows>0)){
 
−
            $row = $result->fetch_assoc();
 
−
            printf("<h1>N&uacute;mero de libros:%d</h1>",$row['num_libros']);
 
−
            $result->free();
 
−
        }
 
−
        else {
 
−
            echo "Fallou a chamada á función.: (" . $conex->errno . ") " . $conex->error;
 
−
        }
 
−
        ?>       
 
−
       
 
−
       
 
−
    </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();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
=====Empregando PDO=====
 
−
* '''Aqruivo bd.php'''
 
−
: Definimos a constante que vai chamar á función.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('FUNCION_CONSULTAR_NUM_LIBROS','select CONSULTAR_NUM_LIBROS() as num_libros');
 
−
</syntaxhighlight>
 
−
 
 
−
: Fixarse no alias da chamada á función.
 
−
 
 
−
 
 
−
* '''Creamos unha páxina consultar_num_libros_con_funcionMYSQL.php'''
 
−
::<syntaxhighlight lang="php" 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);
 
−
 
 
−
?>
 
−
 
 
−
 
 
−
    <!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>Operacións BD</title>
 
−
    </head>
 
−
 
 
−
    <body>
 
−
        <?php
 
−
 
 
−
        $stmt = $conex->prepare(FUNCION_CONSULTAR_NUM_LIBROS);
 
−
        $stmt->execute();
 
−
 
 
−
        $res = $stmt->fetch(PDO::FETCH_ASSOC);
 
−
        if ($res) {
 
−
            printf("<h1>N&uacute;mero de libros:%d</h1>", $res['num_libros']);
 
−
        }
 
−
        ?>
 
−
    </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>
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
 
 
−
=== Paxinación de resultados ===
 
−
 
 
−
* En moitas ocasións imos necesitar paxinar os resultados dunha consulta.
 
−
: Isto é necesario cando o resultado pode devolver unha cantidade moi grande de rexistros que obrigaría ao usuario a facer demasiado scroll.
 
−
 
 
−
 
 
−
* A idea é moi simple. Utilizando a cláusula [https://dev.mysql.com/doc/refman/5.5/en/limit-optimization.html '''LIMIT''' da orde SQL Select].
 
−
: Dita cláusula pode levar dous parámetros separados por coma: LIMIT numero_fila,tamaño e vai ao final da orde SELECT.
 
−
 
 
−
: Por exemplo:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="4" >
 
−
SELECT id,titulo
 
−
FROM libros
 
−
ORDER by titulo
 
−
LIMIT 0,2
 
−
</syntaxhighlight>
 
−
 
 
−
:: Este exemplo amosaría os dous primeiros rexistros empezando polo primeiro do conxunto de resultados (rexistros 0 e 1)
 
−
 
 
−
: Se modifico a orde e poño:
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="4" >
 
−
SELECT id,titulo
 
−
FROM libros
 
−
ORDER by titulo
 
−
LIMIT 2,2
 
−
</syntaxhighlight>
 
−
 
 
−
:: Devolverá os rexistros 2 e 3 do conxunto de resultados. Estamos paxinando os resultados de dous en dous !!!!
 
−
 
 
−
 
 
−
* Polo tanto para paxinar os datos dunha consulta terei que:
 
−
:* Establecer o número de rexistros por páxina. Isto será o que leve a cláusula LIMIT como segundo dato.
 
−
:* Gardar a páxina actual e mandar dito dato entre as diferentes peticións da páxina PHP (normalmente enlaces có número de páxina).
 
−
:* Para saber o número de páxinas terei que obter o número de rexistros en total, xa que o número de páxinas será igual ao número de rexistros dividido (parte enteira) entre o número de rexistros por páxina.
 
−
:: Para isto faremos uso da [http://php.net/manual/es/function.ceil.php función ceil] que devolve o número enteiro redondeado cara arriba.
 
−
:: Así, se teño 10 elementos e quero paxinar 3 elementos por páxina: ceil(10/3) = '''4''' que serán o número de páxinas.
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
==== Caso Práctico: Paxinación de resultados ====
 
−
 
 
−
 
 
−
* Imos crear unha nova páxina de nome '''consultar_libros_con_paxinacion.php''' que partindo do código da páxina 'consultar_libros.php' feita previamente, amose os libros con paxinación.
 
−
 
 
−
: O faremos utilizando un procedemento almacenado (de nome CONSULTAR_LIBROS_PAXINACION), que vai levar dous parámetros, o número de páxina actual e o número de libros por páxina (tamén se podería facer directamente cun select dende PHP sen utilizar procedementos almacenados, pero xa vimos que é máis seguro).
 
−
 
 
−
 
 
−
* Aplicado ao noso exemplo, imos engadir máis datos para poder facer unha paxinación.
 
−
 
 
−
: '''Táboa autores'''
 
−
 
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('ABBOTT', 'RACHEL');
 
−
INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('HJORTH', 'MICHAEL');
 
−
INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('LAURENS', 'STEPHANIE');
 
−
INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('ESTRINGANA', 'MORUENA');
 
−
INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('NIXON', 'RITA');
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
: '''Táboa libros'''
 
−
 
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
INSERT INTO `php`.`libros` (`titulo`) VALUES ('COMO UNA EXTRAÑA');
 
−
INSERT INTO `php`.`libros` (`titulo`) VALUES ('CRÍMENES DUPLICADOS');
 
−
INSERT INTO `php`.`libros` (`titulo`) VALUES ('A SALVO CON SU CAPTOR');
 
−
INSERT INTO `php`.`libros` (`titulo`) VALUES ('MI ERROR FUE NO SER YO MISMA. PARTE II');
 
−
INSERT INTO `php`.`libros` (`titulo`) VALUES ('EL AMOR NO ES UN INVENTO DE LOS POETAS');
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
: '''Táboa libro_autor'''
 
−
:: Teredes que ter en conta os identificadores autonuméricos xerados nos insert´s anteriores.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('3', '2');
 
−
INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('4', '3');
 
−
INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('5', '4');
 
−
INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('6', '5');
 
−
INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('7', '6');
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
: '''Táboa exemplares'''
 
−
:: Teredes que ter en conta os identificadores autonuméricos xerados nos insert´s da táboa libros.
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `prezo`, `estado_id`) VALUES ('3', '1', '9.99', '1');
 
−
INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `prezo`, `estado_id`) VALUES ('3', '2', '9.99', '1');
 
−
INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `imaxe`, `prezo`, `estado_id`) VALUES ('4', '1', '', '29.99', '1');
 
−
INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `prezo`, `estado_id`) VALUES ('5', '1', '23.35', '1');
 
−
INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `imaxe`, `prezo`, `estado_id`) VALUES ('6', '1', '', '34.76', '1');
 
−
INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `imaxe`, `prezo`, `estado_id`) VALUES ('7', '1', '', '22.54', '1');
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
* Creamos o procedemento almacenado en Mysql para que devolva os libros con paxinación.
 
−
: Poderíamos modificar o que xa tiñamos, comprobando se enviamos valores aos parámetros ou non (valor null) e facendo un select ou outro en función dos valores...
 
−
 
 
−
'''Procedemento almacenado CONSULTAR_LIBROS_PAXINACION en Mysql'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
DELIMITER $$
 
−
 
 
−
CREATE DEFINER=`root`@`localhost` PROCEDURE `CONSULTAR_LIBROS_PAXINACION`(IN p_num_rexistro int,IN p_tam_paxina INT)
 
−
BEGIN
 
−
SELECT id,titulo
 
−
FROM libros
 
−
ORDER BY titulo
 
−
LIMIT p_num_rexistro,p_tam_paxina;
 
−
 
 
−
END$$
 
−
</syntaxhighlight>
 
−
 
 
−
 
 
−
 
 
−
* Creamos a constante que vai facer uso deste procedemento.
 
−
'''Arquivo: bd.inc.php'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="" >
 
−
define('PROCEDEMENTO_CONSULTAR_LIBROS_PAXINACION','CONSULTAR_LIBROS_PAXINACION(%d,%d)');
 
−
</syntaxhighlight>
 
−
 
 
−
:: Como vemos imos enviar dous parámetros.
 
−
 
 
−
 
 
−
 
 
−
 
 
−
* Partimos do código da páxina consultar_libros.php e <u>creamos unha nova páxina</u> de nome '''consultar_libros_con_paxinacion.php'''
 
−
: Nesta páxina voi definir nunha constante o número de libros por páxina. Se este dato fora compartido por outras páxinas, debería no arquivo bc.inc.php
 
−
 
 
−
 
 
−
: '''Arquivo: consultar_libros_con_paxinacion.php'''
 
−
 
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="8,31-42,48-68,71-72" >
 
−
<?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');
 
−
define('CONST_NUM_LIBROS_PAXINA',2);    // Podería ir en bd.inc.php ou noutro arquivo inc.php se vai ser empregada por outras páxinas
 
−
 
 
−
 
 
−
try {
 
−
    $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD);  // Non indicamos o nome da bd
 
−
    $conex->set_charset('utf8mb4');
 
−
    $conex->select_db(BD); // O parámetro indica o nome da base de datos
 
−
 
 
−
?>
 
−
 
 
−
<!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>Operacións BD</title>
 
−
    </head>
 
−
 
 
−
    <body>
 
−
        <?php
 
−
                // OBTEMOS O NUMERO DE LIBROS
 
−
                // Este código ven dun caso práctico anterior que devolve o número de libros
 
−
                //  facendo uso dun procedemento almacenado e dun parámetro de saída.
 
−
                $numLibros=0;
 
−
                $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
 
−
                if ($conex->multi_query($proc)){
 
−
                    $result = $conex->store_result();
 
−
                    if (!($resultado = $conex->query("SELECT @numero_libros as _p_out"))) {
 
−
                        die ("Fallou a obtención: (" . $conex->errno . ") " . $conex->error);
 
−
                    }
 
−
                    else {
 
−
                        $fila = $resultado->fetch_assoc();
 
−
                        $numLibros = $fila['_p_out'];
 
−
                    }
 
−
                }
 
−
            ?>
 
−
 
 
−
           
 
−
            <?php  // $numLibros ten o número de libros da nosa táboa
 
−
                // AMOSAMOS AS PAXINAS EN BASE AO NUMERO TOTAL E O NUMERO POR PAXINA
 
−
                $total_paxinas = ceil($numLibros/CONST_NUM_LIBROS_PAXINA);
 
−
               
 
−
                // Establecemos cal é a páxina actual. Se o usuario elixiu algunha virá en forma de URL $_GET
 
−
                if(!empty($_GET['paxina'])){
 
−
                    $paxina_actual=htmlentities($_GET['paxina']);
 
−
                }
 
−
                else{
 
−
                    $paxina_actual=1;  // A primeira vez o usuario non pulsou ningunha páxina e polo tanto a páxina inicial é a 1
 
−
                }
 
−
               
 
−
                // 'Imprimimos as páxinas...
 
−
                echo "<div>";
 
−
                for($cont=1;$cont<=$total_paxinas;$cont++){
 
−
                    if ($cont==$paxina_actual){  // A páxina actual non leva link
 
−
                        echo $cont . ' ';
 
−
                    }
 
−
                    else{
 
−
                        printf("<a href='%s?paxina=%d'>%d</a> ",htmlentities($_SERVER['PHP_SELF']),$cont,$cont);
 
−
                    }
 
−
                }
 
−
                echo "</div>";
 
−
            ?>
 
−
            <?php       
 
−
                $rexistro_actual=($paxina_actual-1)*CONST_NUM_LIBROS_PAXINA;  // Restamos 1 á páxina actual xa que a páxina 1 empeza no rexistro 0
 
−
                $proc=sprintf(PROCEDEMENTO_CONSULTAR_LIBROS_PAXINACION,$conex->real_escape_string($rexistro_actual),CONST_NUM_LIBROS_PAXINA);   
 
−
                $proc = sprintf("CALL %s",$proc);
 
−
                if ($conex->multi_query($proc)){
 
−
                    $result = $conex->store_result();
 
−
                    if ($result->num_rows>0){
 
−
                        echo "<table border='1'>";
 
−
                        while($row = $result->fetch_assoc()) {
 
−
                            echo '<tr>';
 
−
                            printf("<td>%s</td>",$row["titulo"]);
 
−
                            echo '</tr>';
 
−
                        }
 
−
                        echo '</table>';
 
−
                        $result->free();  // Liberamos da memoria os recursos
 
−
                    }
 
−
                    else {
 
−
                        echo "Non hai datos que amosar!!!!";
 
−
                    }
 
−
                }
 
−
        ?>       
 
−
 
 
−
    </body>   
 
−
</html>
 
−
 
 
−
<?php
 
−
}    // Pecha o try do principio 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();
 
−
  }
 
−
}
 
−
?>
 
−
</syntaxhighlight>
 
−
 
 
−
* Liña 8: Indicamos o número de libros a amosar en cada páxina.
 
−
* Liñas 31-42: Obtemos o número de libros que temos na base de datos. Estamos a aproveitar o [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Caso_pr.C3.A1ctico:_Consulta_chamando_a_procedementos_almacenados_con_par.C3.A1metros_de_sa.C3.ADda código dun exercicio anterior] no que amosábamos como traballar cun parámetro de saída, pero neste caso sería máis sinxelo crear un novo procedemento ou unha función, que devolvera o número de libros.
 
−
* Liñas 48-68: Amosamos as páxinas en base ao número de rexistros totais e o número de libros a amosar por páxina.
 
−
* Liñas 71-72: Pasamos ao procedemento almacenado a partires de que número de libro e a cantidade de libros que vai devolver o select no procedemento almacenado.
 
−
 
 
−
 
 
−
 
 
−
 
 
−
 
 
−
 
 
−
<br />
 
−
===== Optimización =====
 
−
 
 
−
* O exercicio anterior poderíamos optimizalo para non ter que repetir a chamada a consultar o número de rexistros totais.
 
−
: Esta chamada a estamos a facer en cada consulta á páxina.
 
−
: Se non estamos nun entorno multi-usuario no que se poida dar o caso de que se estean a dar de alta novos rexistros de forma continua, podemos optimizar o código e facer que a chamada a consultar o número de rexistros totais se faga unha vez.
 
−
 
 
−
 
 
−
 
 
−
* Deberemos gardar nalgunha variable o número de rexistros totais e que dita información se manteña entre chamadas cando cambiamos de número de páxina.
 
−
* <u>Posibles solucións</u>:
 
−
:* Utilizar coookies.
 
−
:* Utilizar variables de sesión.
 
−
:* Aproveitar o formulario da paxinación e enviar en forma de campo oculto o número de rexistros totais.
 
−
:* Enviar o dato na URL a maiores do número de páxina.
 
−
 
 
−
 
 
−
 
 
−
* Posible solución enviando o número total de libros na URL:
 
−
 
 
−
'''Arquivo consultar_libros_con_paxinacion.php'''
 
−
::<syntaxhighlight lang="php" enclose="div" highlight="9-11,47" >
 
−
.......................
 
−
 
 
−
 
 
−
 
 
−
            <?php
 
−
                // OBTEMOS O NUMERO DE LIBROS
 
−
                // Este código ven dun caso práctico anterior que devolve o número de libros
 
−
                //  facendo uso dun procedemento almacenado e dun parámetro de saída.
 
−
                if (!empty($_GET['numtotal'])){ // Ven o número de libros totais na URL
 
−
                    $numLibros = htmlentities($_GET['numtotal']);   
 
−
                }
 
−
                else{
 
−
                    $numLibros=0;
 
−
                    $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
 
−
                    if ($conex->multi_query($proc)){
 
−
                        $result = $conex->store_result();
 
−
                        if (!($resultado = $conex->query("SELECT @numero_libros as _p_out"))) {
 
−
                            die ("Fallou a obtención: (" . $conex->errno . ") " . $conex->error);
 
−
                        }
 
−
                        else {
 
−
                            $fila = $resultado->fetch_assoc();
 
−
                            $numLibros = $fila['_p_out'];
 
−
                        }
 
−
                    }
 
−
   
 
−
                }
 
−
            ?>
 
−
            <?php  // $numLibros ten o número de libros da nosa táboa
 
−
                // AMOSAMOS AS PAXINAS EN BASE AO NUMERO TOTAL E O NUMERO POR PAXINA
 
−
                $total_paxinas = ceil($numLibros/CONST_NUM_LIBROS_PAXINA);
 
−
               
 
−
                // Establecemos cal é a páxina actual. Se o usuario elixiu algunha virá en forma de URL $_GET
 
−
                if(!empty($_GET['paxina'])){
 
−
                    $paxina_actual=htmlentities($_GET['paxina']);
 
−
                }
 
−
                else{
 
−
                    $paxina_actual=1;  // A primeira vez o usuario non pulsou ningunha páxina e polo tanto a páxina inicial é a 1
 
−
                }
 
−
               
 
−
                // 'Imprimimos as páxinas...
 
−
                echo "<div>";
 
−
                for($cont=1;$cont<=$total_paxinas;$cont++){
 
−
                    if ($cont==$paxina_actual){  // A páxina actual non leva link
 
−
                        echo $cont . ' ';
 
−
                    }
 
−
                    else{
 
−
                        printf("<a href='%s?paxina=%d&numtotal=%d'>%d</a> ",htmlentities($_SERVER['PHP_SELF']),$cont,$numLibros,$cont);
 
−
                    }
 
−
                }
 
−
                echo "</div>";
 
−
            ?>
 
  
−
</syntaxhighlight>
+
| ! Uso                                                                                            |
 +
| ------------------------------------------------------------------------------------------------- |
 +
| `new PDO(...)`                                                                                    |
 +
| Abrir unha conexión coa base de datos.                                                            |
 +
| -                                                                                                |
 +
| `query()`                                                                                        |
 +
| Executar unha sentenza SQL sen parámetros externos e obter un resultado.                          |
 +
| -                                                                                                |
 +
| `prepare()`                                                                                      |
 +
| Preparar unha sentenza SQL parametrizada.                                                        |
 +
| -                                                                                                |
 +
| `execute()`                                                                                      |
 +
| Executar unha sentenza preparada.                                                                |
 +
| -                                                                                                |
 +
| `bindValue()`                                                                                    |
 +
| Asociar un valor e, opcionalmente, o seu tipo a un parámetro.                                    |
 +
| -                                                                                                |
 +
| `fetch()`                                                                                        |
 +
| Recuperar unha fila do resultado.                                                                |
 +
| -                                                                                                |
 +
| `fetchAll()`                                                                                      |
 +
| Recuperar todas as filas do resultado.                                                            |
 +
| -                                                                                                |
 +
| `fetchColumn()`                                                                                  |
 +
| Recuperar o valor dunha columna, especialmente útil para `COUNT(*)` ou funcións.                  |
 +
| -                                                                                                |
 +
| `closeCursor()`                                                                                  |
 +
| Liberar os resultados pendentes dunha sentenza, especialmente útil con procedementos almacenados. |
 +
| -                                                                                                |
 +
| `PDO::PARAM_INT`                                                                                  |
 +
| Indica un parámetro de tipo enteiro.                                                              |
 +
| -                                                                                                |
 +
| `PDO::PARAM_STR`                                                                                  |
 +
| Indica un parámetro de tipo cadea.                                                                |
 +
| -                                                                                                |
 +
| `PDO::PARAM_BOOL`                                                                                |
 +
| Indica un parámetro booleano.                                                                    |
 +
| -                                                                                                |
 +
| `PDO::PARAM_NULL`                                                                                |
 +
| Indica un valor nulo.                                                                            |
 +
| -                                                                                                |
 +
| `PDO::FETCH_ASSOC`                                                                                |
 +
| Recupera as filas como arrays asociativos.                                                        |
 +
| }                                                                                                |
 +
::</syntaxhighlight>
  
 +
== Regras que empregaremos nos exames ==
  
 +
# As operacións contra a base de datos realizaranse mediante '''PDO'''.
  
−
<br />
+
# Para consultas sen parámetros externos poderá empregarse `query()`.
−
* Posible solución utilizando [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Sesi%C3%B3ns variables de sesión] (veremos o que son nas seguintes seccións):
 
  
−
'''Arquivo consultar_libros_con_paxinacion.php'''
+
# Se hai datos procedentes do usuario ou outros valores externos empregarase `prepare()` + `execute()`.
−
::<syntaxhighlight lang="php" enclose="div" highlight="1-4,9,21,29" >
 
−
<?php
 
−
  session_start();  // Iría ao principio de todo
 
  
−
?>
+
# Non se concatenarán directamente datos externos nas sentenzas SQL.
−
.......................
 
  
 +
# Os datos recibidos mediante formularios ou URL deberán validarse.
  
−
            <?php
+
# Cando o tipo sexa relevante empregarase `bindValue()` indicando `PDO::PARAM_INT`, `PDO::PARAM_STR`, etc.
−
                if (empty($_SESSION['numRexistrosTotais']) {
 
−
                  // OBTEMOS O NUMERO DE LIBROS
 
−
                  $numLibros=0;
 
−
                  $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
 
−
                  if ($conex->multi_query($proc)){
 
−
                      $result = $conex->store_result();
 
−
                      if (!($resultado = $conex->query("SELECT @numero_libros as _p_out"))) {
 
−
                          die ("Fallou a obtención: (" . $conex->errno . ") " . $conex->error);
 
−
                      }
 
−
                      else {
 
−
                          $fila = $resultado->fetch_assoc();
 
−
                          $numLibros = $fila['_p_out'];
 
−
                          $_SESSION['numRexistrosTotais'] = $numLibros;
 
−
                      }
 
−
                  }
 
−
                }
 
−
                else {
 
−
                    $numLibros = $_SESSION['numRexistrosTotais'];
 
−
                }
 
−
            ?>
 
−
            ...................
 
  
−
</syntaxhighlight>
+
# Non se empregará `rowCount()` para coñecer o número de filas devoltas por un `SELECT`.
  
 +
# Se necesitamos contar filas empregaremos `COUNT(*)`.
  
 +
# Para amosar cadeas dentro de HTML empregarase `htmlspecialchars()`.
  
 +
# Os erros PDO capturaranse mediante `PDOException`.
  
 +
# Os detalles técnicos dos erros rexistraranse mediante `error_log()` e non se amosarán ao usuario final.
  
 +
# Os demais erros ou excepcións poderán capturarse mediante `Throwable`.
  
 +
# Os exemplos realizaranse tomando como referencia '''PHP 8.5'''.
  
 
<br />
 
<br />
−
=== Exercicio proposto ===
 
−
 
−
* Crea unha páxina de nome '''buscar_libros.php''' no que se amose nun formulario as seguintes opcións de busca de libros:
 
−
:* Busca por título: Será unha caixa de texto limitada a 50 caracteres que fará uso de like (busca por patróns)
 
−
:* Busca por prezo: Estará implementada en forma de caixa limitada a 6 caracteres (tres números, unha coma e dous decimais). Deberedes verificar que o formato sexa correcto.
 
−
:* Busca por estado: Estará implementada en forma de lista (<select> en html con size) (opcionalmente podedes permitir que o usuario poida escoller varios estados).
 
−
:* Busca por autor: Estará implementada en forma de combobox (<select> en html)
 
−
 
−
:* Criterios de ordenación: Por título, prezo e autor (implementalo con radiobuttons).
 
−
 
−
* O usuario vai poder escoller calquera das opcións combinándoas como prefira.
 
−
* Os resultados van poder amosarse paxinados. O número de libros por páxina estará indicado nunha caixa de texto co seu propio formulario.
 
−
 
−
* O resultado vai ser unha lista de libros, os seus autores e os exemplares, ordenados por título
 
−
* Implementa a solución utilizando consultas.
 
−
 
−
* Quen queira tamén pode intentalo facer con procedementos almacenados pero a consulta teredes que facela dinamicamente dentro do procedementos.
 
−
: Podedes consultar como facelo [https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html neste enlace].
 
−
 
  
  

Revisión actual del 18:12 8 oct 2026

Sumario

Introducción


IMPORTANTE: Nos exames, exercicios e prácticas avaliables empregarase PDO para realizar as operacións contra a base de datos, salvo que no enunciado se indique expresamente o contrario. Os exemplos con mysqli poden manterse nos apuntes a nivel informativo, pero os exemplos de referencia serán os realizados con PDO.



  • O proceso para realizar unha consulta é o seguinte:


  • Necesitamos ter unha consulta SQL para recuperar datos.
Indicar que o SQL (Structure Query Language) é a linguaxe utilizada para obter datos dunha base de datos relacional.
Indicar que estos contidos xa deben estar aprendidos do módulo de Base de datos e saber facer joins entre táboas, subconsultas, agrupacións,...


  • CONSIDERACIÓNS:
  • Crear un usuario/password con permisos de acceso a dita base de datos (sempre dádelle os mínimos permisos posibles, neste caso, serán os de SELECT/UPDATE/DELETE/INSERT/EXECUTE de toda a base de datos)
  • Crear nun arquivo separado do sitio web todos os datos necesarios para conectarse á base de datos e onde van estar definidas as constantes coas ordes SQL, tal como vimos nun punto anterior desta sección.
  • Lembrar poñer as ordes para amosar os erros que se poidan dar. Nunha aplicación final esas ordes hai que quitalas.
  • En Linux, lembrar dar permiso 644 (rw,r,r) aos arquivos php e cambiar o propietario a www-data:www-data. Nos cartafoles permisos 755 cando rematedes.
  • Ao principio ides faiar en moitas cousas. Lembrade ter posto o display_errors como vimos nas seccións anteriores. Tamén axuda moito facer un echo da sentenza a enviar á base de datos antes de enviala para ver que estea todo ben (espazos en branco, comillas,...).
  • Lembrar que tamén podedes activar o debugger como está indicado nesta sección da wiki.



Realización dunha consulta SELECT

  • Paso 2: Chamar ao procedemento query enviando como parámetro a consulta a realizar e a conexión aberta previamente.
Consulta SQL: SELECT col1, col2 FROM TABOA_1 ORDER BY col
  • Se a consulta SQL non leva datos externos podemos empregar directamente o método query():
$stmt = $conex->query(
'SELECT col1, col2
FROM TABOA_1
ORDER BY col1'
);
query() devolve un obxecto da clase PDOStatement.


  • Se a consulta leva datos procedentes do usuario ou valores que queremos enviar mediante parámetros empregaremos:
prepare()
execute()
Por exemplo:
$stmt = $conex->prepare(
'SELECT col1, col2
FROM TABOA_1
WHERE col1 = :valor'
);
$stmt->execute([
'valor' => 5
]);




  • Version procedimental
Funcións para realizar consultas a base de datos e funcións relacionadas:
  • mysqli::query: Realiza a consulta
  • mysqli_result: En caso de realizar unha consulta de tipo 'select' este obxecto representa o conxunto de resultados obtidos.
  • int $mysqli_result->num_rows: Devolve o número de filas devoltas en caso de realizar unha consulta.
  • Para recuperar os datos podemos empregar:
  • array mysqli_result::fetch_assoc(void): Devolve nun array asociativo unha das filas do resultado da consulta e 'pasa' á fila seguinte. Cando remate e xa non teña máis filas devolve NULL. A clave do array asociativo é cada un dos nomes das columnas que conforman a consulta.
  • mysqli_result::fetch_object(void): Devolve nun array de obxectos todas as filas do resultado da consulta e 'pasa' á fila seguinte. Cando remate e xa non teña máis filas devolve NULL. Cada obxecto terá como propiedades cada unha das columnas que conforman a consulta (distingue maiúsculas de minúsculas).


NOTA: En todos os exercicios e exemplos vou facer uso de fetch_assoc para recuperar os datos, pero quen queira pode empregar fech_object. O cambio é case inmediato, o único será o acceso as propiedades que se fai có operador: ->


  • Versión procedimental:
$result=mysqli_query($conex, 'SELECT col1, col2 FROM TABOA_1 ORDER BY col1');
Nota: $conex é a conexión aberta feita no paso anterior.
  • Versión orientada a obxectos e PDO:
$result=$conex->query('SELECT col1, col2 FROM TABOA_1 ORDER BY col1');
  • $result vai ter o conxunto de filas que devolva o resultado. Podemos velo como unha táboa formada polas filas do resultado e polas columnas indicadas no select.



Comprobar se unha consulta SELECT devolveu datos

  • Non debemos empregar rowCount() para comprobar o número de filas devoltas por un SELECT.
rowCount() está pensado principalmente para coñecer o número de filas afectadas por operacións INSERT, UPDATE ou DELETE.
Nun SELECT o seu comportamento depende do controlador da base de datos e non está garantido.


  • Unha forma sinxela de comprobar se hai datos consiste en realizar un primeiro fetch():
$stmt = $conex->query(
'SELECT col1, col2
FROM TABOA_1
ORDER BY col1'
);
$fila = $stmt->fetch();
if ($fila !== false) {
do {

    echo htmlspecialchars(
        $fila['col1'],
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );

    echo ' - ';

    echo htmlspecialchars(
        $fila['col2'],
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );

    echo '<br>';

} while ($fila = $stmt->fetch());
} else {
echo 'Non hai datos que amosar';
}
  • Se sabemos que o número de filas vai ser pequeno tamén podemos recuperar todas as filas mediante fetchAll():
$stmt = $conex->query(
'SELECT col1, col2
FROM TABOA_1
ORDER BY col1'
);
$filas = $stmt->fetchAll();
if ($filas) {
foreach ($filas as $fila) {

    echo htmlspecialchars(
        $fila['col1'],
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );

    echo ' - ';

    echo htmlspecialchars(
        $fila['col2'],
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );

    echo '<br>';
}
} else {
echo 'Non hai datos que amosar';
}


  • fetchColum permite recuperar da primeira fila a columna indicada. Se non poñemos ningunha columna recuperar a primeira.
Por exemplo, se realmente necesitamos coñecer o número de rexistros, realizaremos unha consulta específica empregando COUNT(*) e como sabemos que vai devolver unha fila cunha columna podemos empregar fetchColumn:
$stmt = $conex->query(
'SELECT COUNT(*)
FROM libros'
);
$numLibros = $stmt->fetchColumn();




  • Versión orientada a obxectos:


  • Se estamos a empregar unha versión PHP superior ou igual á 8.1, os erros deben ser capturados con try-catch:
  • Versión orientada a obxectos:
<?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);

try {
    $conex = new mysqli('SERVIDOR','USUARIO','PASSWORD','BD');   
    $conex->set_charset('utf8mb4');

?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Operacións BD</title>
    </head>

    <body>
    <?php        
        
        $result=$conex->query('SELECT col1, col2 FROM TABOA_1 ORDER BY col1');
        if ($result && $result->num_rows>0){
                // Aquí procesamos os datos
        }
        else {
            echo "Non hai datos que amosar!!!!";
        }
        
    ?>        
    </body>    
</html>

<?php
}    // Pecha o try do principio da páxina
catch (mysqli_sql_exception $e) {
    die("Erro mysql: " . $e->getMessage());
}
catch (Expcetion $e) {
    die("Erro xeral: " . $e->getMessage());
}

?>



  • Como vemos, en caso de erro comprobamos se o xestor devolveu un código de erro, indicativo de que a sentenza SQL está mal feita. A información do código de erro non debe amosarse a un usuario final. Normalmente, se pon o echo de 'Non hai datos que amosar' e se estamos a desenvolver a aplicación e non devolve datos, faríamos un echo temporal do código de erro do xestor ou un echo da sentenza SQL, podendo executala no xestor Mysql para ver o erro.




Recuperación de filas

Paso 4: Agora necesitaremos percorrer dende o primeiro rexistro ata o último amosando o resultado.

  • O método fetch() recupera unha única fila do conxunto de resultados e avanza á seguinte.
while ($fila = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo htmlspecialchars(
    $fila['titulo'],
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);
}
Fixarse que neste exemplo no facemos un if inicial có primeiro fetch para saber si hai datos ou non. Dependerá de vos como programadores determinar se é necesario para informar ao usuario o facer algún tipo de proceso nese caso.


  • Se na conexión establecemos:
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
non será necesario indicar PDO::FETCH_ASSOC en cada chamada:
while ($fila = $stmt->fetch()) {
echo $fila['titulo'];
}
PDO::FETCH_ASSOC o que fai é que Php soamente cre un array asociativo cós resultados. Se non o poñemos, devolve dous arrays, un no que se accede aos datos polo índice ($fila[0]) e outro asociativo por nome de columna ($fila['titulo']).


  • Non debemos chamar dúas veces seguidas a fetch() para procesar o mesmo conxunto de resultados.
Por exemplo, isto é incorrecto:
while ($fila = $stmt->fetch(PDO::FETCH_OBJ)) {
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    // ...
}
}
O primeiro fetch() xa consume unha fila e o segundo bucle comezaría na seguinte.
A forma correcta sería:
while ($row = $stmt->fetch()) {
// Procesamos a fila
}




  • Versión procedimental
Debemos entender que, inicialmente, despois de facer a consulta, temos un punteiro que está 'apuntando' xusto antes do primeiro rexistro de resultados. O que fai a función anterior é mover ese punteiro ao seguinte rexistro (a primeira vez apuntará ao primeiro do conxunto de resultados) e devolve nun array a fila á que está 'apuntando'.
Se volvemos a chamar, volve a moverse ao seguinte rexistro e así ata ao final no que devolve false.


Isto o faremos cun bucle, chamando á función mysqli_result::fetch_assoc(void).
  • Versión procedimental:
if ($result && (mysqli_num_rows($resullt)>0)){
  // Veñen datos
   while($row = mysqli_fetch_assoc($result)) {
    printf("%s-%s",htmlentities($row["col1"]),htmlentities($row["col2"]));
   }
}
else {
  echo 'Non hai datos ou a consulta está mal feita';
}


  • Versión orientada a obxectos:
if ($result && ($result->num_rows>0)){
  // Veñen datos
   while($row = $result->fetch_assoc()) {
    printf("%s-%s",htmlentities($row["col1"]),htmlentities($row["col2"]));
   }
}
else {
  echo 'Non hai datos ou a consulta está mal feita';
}


  • Versión orientada a obxectos con fetch_object:
if ($result && ($result->num_rows>0)){
  // Veñen datos
   while($row = $result->fetch_object()) {
    printf("%s-%s",htmlentities($row->col1),htmlentities($row->col2));
   }
}
else {
  echo 'Non hai datos ou a consulta está mal feita';
}

Saída de datos en HTML

Para amosar datos dentro dunha páxina HTML empregaremos normalmente htmlspecialchars():

echo htmlspecialchars(
$fila['titulo'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);

Non debemos empregar urlencode() para codificar texto que simplemente se vai amosar ao usuario.

Por exemplo, para crear un enlace a un autor:
printf(
"<a href='detalle_autor.php?id=%d'>%s, %s</a>",
(int) $row['id'],
htmlspecialchars(
$row['apelidos'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
),
htmlspecialchars(
$row['nome'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
)
);
urlencode() ou rawurlencode() utilizaranse cando sexa necesario codificar datos que forman parte dunha URL.

Consultas con parámetros numéricos

Podemos empregar parámetros con nome:

define(
'CONSULTA_CON_PARAM_NUMERICO',
'SELECT col1, col2
FROM TABOA_1
WHERE col1 = :valor'
);

Preparamos e executamos a consulta:

$stmt = $conex->prepare(
CONSULTA_CON_PARAM_NUMERICO
);
$stmt->execute([
'valor' => 5
]);
IMPORTANTE: O nome do parámetro debe coincidir co definido na consulta.
Se na consulta temos:
WHERE col1 = :valor
debemos empregar:
'valor' => 5
e non outro nome como valor1.

Tipo dos parámetros

Cando enviamos directamente un array ao método execute():

$stmt->execute([
'valor' => 5
]);
PDO trata os valores do array como PDO::PARAM_STR.
O xestor de base de datos pode realizar posteriormente conversións de tipo segundo o contexto da consulta.

Na maioría das consultas sinxelas isto non supón un problema. Se queremos indicar explicitamente o tipo do parámetro podemos empregar bindValue():

$stmt = $conex->prepare(
'SELECT col1, col2
FROM TABOA_1
WHERE col1 = :valor'
);
$stmt->bindValue(
':valor',
5,
PDO::PARAM_INT
);
$stmt->execute();

Algúns tipos habituais son:

PDO::PARAM_INT
PDO::PARAM_STR
PDO::PARAM_BOOL
PDO::PARAM_NULL


  • É especialmente recomendable indicar explicitamente o tipo cando este sexa relevante para a consulta.
Por exemplo, nos parámetros empregados en LIMIT ou OFFSET:
$stmt = $conex->prepare(
'SELECT id, titulo
FROM libros
ORDER BY titulo
LIMIT :inicio,:cantidade'
);
$stmt->bindValue(
':inicio',
$inicio,
PDO::PARAM_INT
);
$stmt->bindValue(
':cantidade',
$cantidade,
PDO::PARAM_INT
);
$stmt->execute();

Parámetros de tipo cadea

Para enviar unha cadea empregaremos tamén unha sentenza preparada:

$stmt = $conex->prepare(
'SELECT id, apelidos, nome
FROM autores
WHERE apelidos = :apelidos'
);
$stmt->execute([
'apelidos' => $apelidos
]);


  • Se queremos facer unha busca mediante LIKE, o marcador debe representar o valor completo:
$stmt = $conex->prepare(
'SELECT id, apelidos, nome
FROM autores
WHERE apelidos LIKE :apelidos'
);
$stmt->execute([
'apelidos' => '%' . $apelidos . '%'
]);


  • Non debemos escribir:
WHERE apelidos LIKE '%:apelidos%'
xa que o marcador debe ocupar o lugar dun valor completo (PHP/PDO interpretará :apelidos como texto literal dentro das comiñas e non o substituirá pola variable).

Validación dos parámetros recibidos

As sentenzas preparadas evitan que os valores enviados como parámetros sexan interpretados como parte da instrución SQL, pero non substitúen a validación dos datos. Por exemplo, se esperamos recibir un identificador numérico desde un formulario:

$libroId = filter_input(
INPUT_POST,
'lstLibros',
FILTER_VALIDATE_INT
);
if (
$libroId !== false &&
$libroId !== null &&
$libroId !== -1
) {
$stmt = $conex->prepare(
    CONSULTAR_EXEMPLARES_POR_LIBRO
);

$stmt->bindValue(
    ':libro_id',
    $libroId,
    PDO::PARAM_INT
);

$stmt->execute();

// Procesamos os resultados
}

Caso práctico: consultar exemplares dun libro

Arquivo bd.php

define('CARTAFOL_IMAXES_WEB', 'http://localhost/imaxes/');

define(
'CONSULTAR_EXEMPLARES_POR_LIBRO',
'SELECT libro_id,
id_exemplar,
imaxe,
prezo,
estado_id
FROM exemplares
WHERE libro_id = :libro_id
ORDER BY estado_id'
);



  • Código PHP
<html>

<head>Páxina de exemplo
</head>

<body>

    <?php
    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
        );

        $opciones = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false
        ];

        $conex = new PDO(
            $dsn,
            USUARIO,
            PASSWORD,
            $opciones
        );

        $libroId = filter_input(INPUT_POST,'lstLibros',FILTER_VALIDATE_INT);  // Como non temos feito a páxina de formulario na seguinte liña dou un valor específico...
        $libroId = 1;   // Este dato virá dunha caixa de texto dunha páxina anterior que terá como action do formulario esta páxina (pode facerse todo nunha mesma páxina)

        if (
            $libroId !== false &&
            $libroId !== null &&
            $libroId !== -1
        ) {
            $stmt = $conex->prepare(
                CONSULTAR_EXEMPLARES_POR_LIBRO
            );

            $stmt->bindValue(
                ':libro_id',
                $libroId,
                PDO::PARAM_INT
            );

            $stmt->execute();

            $fila = $stmt->fetch();

            if ($fila !== false) {

                echo "<table border='1'>";
                echo '<tr>';
                echo '<th>Num. exemplar</th>';
                echo '<th>Imaxe</th>';
                echo '<th>Prezo</th>';
                echo '<th>Estado</th>';
                echo '</tr>';

                do {

                    echo '<tr>';

                    printf(
                        '<td>%d</td>',
                        (int) $fila['id_exemplar']
                    );

                    printf(
                        "<td><img alt='%s' src='%s' width='42' height='42'></td>",
                        htmlspecialchars(
                            $fila['imaxe'] ?? '',
                            ENT_QUOTES | ENT_SUBSTITUTE,
                            'UTF-8'
                        ),
                        htmlspecialchars(
                            CARTAFOL_IMAXES_WEB . ($fila['imaxe'] ?? ''),
                            ENT_QUOTES | ENT_SUBSTITUTE,
                            'UTF-8'
                        )
                    );

                    printf(
                        '<td>%.2f</td>',
                        (float) ($fila['prezo'] ?? 0)
                    );

                    printf(
                        '<td>%d</td>',
                        (int) $fila['estado_id']
                    );

                    echo '</tr>';
                } while ($fila = $stmt->fetch());

                echo '</table>';
            } else {

                echo 'Non hai datos que amosar';
            }
        }
    } catch (PDOException $e) {
        die("Error PDO: " . $e->getMessage());
    } catch (Throwable $e) {
        die("Error general: " . $e->getMessage());
    }

    $conex = null;   // Poderíamos non poñer nada
    ?>
</body>

</html>

Procedementos almacenados empregando PDO

Os procedementos almacenados execútanse mediante a sentenza SQL CALL.

Por exemplo:
$stmt = $conex->prepare(
'CALL CONSULTAR_LIBROS()'
);
$stmt->execute();

Se o procedemento devolve un conxunto de resultados podemos percorrelo da forma habitual:

while ($fila = $stmt->fetch()) {
echo htmlspecialchars(
    $fila['titulo'],
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);
}

Procedementos con varios conxuntos de resultados

Un procedemento almacenado pode devolver máis dun conxunto de resultados. Para pasar ao seguinte conxunto empregaremos:

nextRowset()
Por exemplo:
$stmt = $conex->prepare(
'CALL PROCEDEMENTO_CON_VARIOS_SELECT()'
);
$stmt->execute();
do {
while ($fila = $stmt->fetch()) {

    // Procesar as filas deste conxunto
}
} while ($stmt->nextRowset());


+ Antes de executar outra consulta na mesma conexión debemos asegurarnos de que non queden resultados pendentes.

Podemos liberar os resultados restantes mediante:
$stmt->closeCursor();
Isto é especialmente importante despois de chamadas a procedementos almacenados en MySQL.


Por exemplo:
<?php
// 1. Executamos o procedemento almacenado que devolve varios SELECTs
$stmt = $conex->prepare('CALL PROCEDEMENTO_CON_VARIOS_SELECT()');
$stmt->execute();

// 2. Procesamos só o primeiro conxunto de resultados (por exemplo, os primeiros 3 datos)
if ($fila = $stmt->fetch()) {
    echo "Primeiro rexistro: " . $fila['nome'] . "<br>";
}

// 3. Pechamos o cursor para liberar a conexión
// Isto descarta calquera outra fila ou segundo resultset pendente
$stmt->closeCursor();

// 4. Agora xa podemos executar outra consulta na mesma conexión sen erros
$outraConsulta = $conex->query('SELECT * FROM outra_taboa');
$datos = $outraConsulta->fetchAll();
?>

Procedementos con parámetros de entrada

Os parámetros de entrada enviaranse empregando marcadores:

$stmt = $conex->prepare(
'CALL PROCEDEMENTO_CON_PARAMETROS(
:dato1,:dato2

)'
);
$stmt->bindValue(
':dato1',
$valorNum,
PDO::PARAM_INT
);
$stmt->bindValue(
':dato2',
$valorTexto,
PDO::PARAM_STR
);
$stmt->execute();
Desta forma non necesitamos construír a chamada mediante sprintf() nin concatenar os valores dentro da sentenza SQL.

Procedementos con parámetros de saída en MySQL

PDO define mecanismos xenéricos para parámetros de saída, pero o controlador PDO_MYSQL non xestiona correctamente PDO::PARAM_INPUT_OUTPUT. Por este motivo, con MySQL podemos empregar unha variable de usuario de MySQL para recuperar un parámetro OUT. Supoñamos este procedemento:

DELIMITER $$
CREATE PROCEDURE CONSULTAR_NUM_LIBROS(
OUT p_num_libros INT
)
BEGIN
SELECT COUNT(*)
INTO p_num_libros
FROM libros;
END$$
DELIMITER ;

Desde PHP podemos facer:

$stmt = $conex->prepare(
'CALL CONSULTAR_NUM_LIBROS(
@numero_libros
)'
);
$stmt->execute();
/*
Liberamos todos os resultados da chamada
antes de executar outra sentenza SQL.
*/
$stmt->closeCursor();
$stmt = $conex->query(
'SELECT @numero_libros'
);
$numLibros = $stmt->fetchColumn();
printf(
'<h1>Número de libros: %d</h1>',
(int) $numLibros
);
É importante chamar a closeCursor() antes do segundo SELECT, xa que MySQL pode manter conxuntos de resultados pendentes da chamada ao procedemento.

Funcións almacenadas de MySQL

As funcións de MySQL poden empregarse dentro dunha instrución SELECT. Se a función non leva parámetros podemos facer:

$stmt = $conex->query(
'SELECT CONSULTAR_NUM_LIBROS()'
);
$numLibros = $stmt->fetchColumn();

Se leva parámetros empregaremos unha sentenza preparada:

$stmt = $conex->prepare(
'SELECT Suma(:dato1,:dato2 )'
);
$stmt->bindValue(
':dato1',
5,
PDO::PARAM_INT
);
$stmt->bindValue(
'':dato2,
7,
PDO::PARAM_INT
);
$stmt->execute();
$resultado = $stmt->fetchColumn();
fetchColumn() resulta especialmente cómodo cando a consulta devolve un único valor.

Paxinación de resultados empregando PDO

Para paxinar resultados empregaremos:

  • COUNT(*) para coñecer o número total de rexistros.
  • LIMIT para indicar a cantidade de rexistros que queremos recuperar.
  • OFFSET, ou o primeiro parámetro de LIMIT, para indicar desde que rexistro queremos comezar.

Por exemplo:

SELECT id, titulo
FROM libros
ORDER BY titulo
LIMIT 10 OFFSET 20
Esta consulta devolvería ata 10 rexistros empezando desde o rexistro situado na posición 20.



Número total de rexistros

$stmt = $conex->query(
'SELECT COUNT(*)
FROM libros'
);
$numLibros = (int) $stmt->fetchColumn();



Páxina actual O número de páxina recibido mediante GET é un dato externo e debe validarse.

$paxinaActual = filter_input(
INPUT_GET,
'paxina',
FILTER_VALIDATE_INT
);
if (
$paxinaActual === false ||
$paxinaActual === null ||
$paxinaActual < 1
) {
$paxinaActual = 1;
}
htmlspecialchars() non serve para validar que un dato sexa un número. Empregarase posteriormente cando teñamos que inserir unha cadea dentro de HTML.



Cálculo do número de páxinas

define(
'CONST_NUM_LIBROS_PAXINA',
10
);
$totalPaxinas = (int) ceil(
$numLibros / CONST_NUM_LIBROS_PAXINA
);

Podemos evitar que se solicite unha páxina superior á existente:

if (
$totalPaxinas > 0 &&
$paxinaActual > $totalPaxinas
) {
$paxinaActual = $totalPaxinas;
}



Calcular o desprazamento

$inicio = (
$paxinaActual - 1
) * CONST_NUM_LIBROS_PAXINA;
Por exemplo, se estamos:
  • Na páxina 1 => inicio = 0
  • Na páxina 2 => inicio = 10
  • Na páxina 3 => inicio = 20



Consulta paxinada Neste caso indicaremos explicitamente que os parámetros son enteiros:

$stmt = $conex->prepare(
'SELECT id, titulo
FROM libros
ORDER BY titulo
LIMIT :limite
OFFSET :desprazamento'
);
$stmt->bindValue(
':limite',
CONST_NUM_LIBROS_PAXINA,
PDO::PARAM_INT
);
$stmt->bindValue(
':desprazamento',
$inicio,
PDO::PARAM_INT
);
$stmt->execute();
$libros = $stmt->fetchAll();


  • Despois podemos amosar os libros:
if ($libros) {
echo "<table border='1'>";

foreach ($libros as $libro) {

    echo '<tr>';

    printf(
        '<td>%s</td>',
        htmlspecialchars(
            $libro['titulo'],
            ENT_QUOTES | ENT_SUBSTITUTE,
            'UTF-8'
        )
    );

    echo '</tr>';
}

echo '</table>';
} else {
echo 'Non hai datos que amosar';
}




Enlaces da paxinación

echo '<div>';
for (
$cont = 1;
$cont <= $totalPaxinas;
$cont++
) {
if ($cont === $paxinaActual) {

    printf(
        '<strong>%d</strong> ',
        $cont
    );

} else {

    printf(
        "<a href='?paxina=%d'>%d</a> ",
        $cont,
        $cont
    );
}
}
echo '</div>';



Sobre a optimización do número total de rexistros En xeral non deberiamos enviar o número total de rexistros na URL confiando despois nese valor:

?paxina=2&numtotal=50
Os datos enviados mediante GET poden ser modificados polo usuario.

Tampouco debemos asumir que gardar o número total nunha variable de sesión é sempre unha optimización correcta, xa que a base de datos pode cambiar e o valor almacenado quedar desactualizado. Como primeira solución, realizaremos en cada petición:

SELECT COUNT(*)
FROM libros
Se nunha aplicación real esta operación chegase a ser un problema de rendemento, poderían estudarse posteriormente técnicas de caché ou outras estratexias de optimización.

Xestión de excepcións

  • Nos exemplos completos empregaremos o seguinte esquema:
try {
    $conex = new PDO($dsn, $usuario, $contrasinal, [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);

    // Operacións coa base de datos...

} catch (PDOException $e) {
    // Rexistrar o erro nun log (monolog, etc.)
    error_log($e->getMessage());
    
    die('Erro relacionado coa base de datos.');
} catch (Throwable $e) {
    die('Produciuse un erro na aplicación.');
}

// Non fai falta "finally {$conex = null;}":
// Se o script continúa, destruirasie cando cumpra o seu ciclo;
// se entra nos catch, o die() xa finaliza o proceso e libera a memoria.
PDOException permite tratar especificamente os erros de PDO.
Throwable permite capturar outros erros ou excepcións producidos durante a execución.
Os bloques catch deben colocarse desde o máis específico ao máis xeral.



Exercicios prácticos

  • Partimos de que xa temos creada a base de datos nunha sección anterior desta wiki e gardados os datos necesarios para realizar a conexión no arquivo `bd.php`.
  • IMPORTANTE: Nos exames, exercicios e prácticas avaliables empregaremos PDO para realizar as operacións contra a base de datos, salvo que no enunciado se indique expresamente o contrario.
  • Os exemplos con `mysqli` poden consultarse como información adicional, pero os exemplos de referencia dos apuntes serán os realizados mediante PDO.
  • Nos exemplos completos empregaremos o seguinte esquema:
No arquivo bd.php que se atopa fora da ruta /var/www/html gardaremos os datos da conexión e as consultar a empregar:
<?php

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

define(
    'NOME_DA_CONSULTA',
    'SELECT col1,col2,...
     FROM taboa
     ORDER BY col1'
);


Nas páxinas PHP que van acceder á base de datos terán este esquema:

try {

require '/var/www/confBD/bd.php';

try {

    $dsn = sprintf(
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
        SERVIDOR,
        BD
    );

    $conex = new PDO(
        $dsn,
        USUARIO,
        PASSWORD,
        [
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]
    );

    // Operacións coa base de datos...


} catch (PDOException $e) {


// Rexistrar o erro nun log.
// Nunha aplicación real tamén poderían empregarse
// ferramentas como Monolog.
error_log($e->getMessage());

die('Erro relacionado coa base de datos.');

} catch (Throwable $e) {

die('Produciuse un erro na aplicación.');

}
  • `PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC` fai que, por defecto, as filas recuperadas mediante `fetch()` ou `fetchAll()` sexan arrays asociativos.
  • `PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION` fai que os erros producidos por PDO xeren unha excepción de tipo `PDOException`.
  • `PDOException` permítenos tratar especificamente os erros relacionados con PDO.
  • `Throwable` permite capturar outros erros ou excepcións producidos durante a execución da aplicación.
  • Os bloques `catch` deben ordenarse desde o máis específico ao máis xeral.
  • Nunha aplicación en produción non debemos amosar ao usuario a mensaxe devolta por `$e->getMessage()`. Podemos rexistrala mediante `error_log()` ou outro sistema de logs.


Caso Práctico 1

  • Imos crear unha consulta que devolva todos os libros da táboa `LIBROS` e os amose dentro dunha táboa HTML.
  • Para non ter as consultas SQL no medio do código das diferentes páxinas, definirémolas no arquivo `bd.php`.

Arquivo: bd.php

define(
'CONSULTAR_LIBROS',
'SELECT id, titulo
FROM libros
ORDER BY titulo'
);
  • Como norma para os nomes das constantes que conteñan sentenzas SQL:
  • Comezarán cun verbo que poderá ser `CONSULTAR`, `INSERIR`, `MODIFICAR` ou `ELIMINAR`.
  • A continuación empregarase un guión baixo.
  • Despois indicarase o obxectivo da operación.

Solución Caso Práctico 1 empregando PDO

Arquivo: consultar_libros.php

<?php

ini_set('display_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,
        [
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]
    );

    $stmt = $conex->query(CONSULTAR_LIBROS);

    $libros = $stmt->fetchAll();

} catch (PDOException $e) {

    error_log($e->getMessage());
    die('Erro relacionado coa base de datos.');

} catch (Throwable $e) {

    die('Produciuse un erro na aplicación.');

}

?>

<!DOCTYPE html>

<html lang="gl">
<head>
    <meta charset="UTF-8">
    <title>Operacións BD</title>
</head>

<body>

<?php

if ($libros) {

    echo "<table border='1'>";

    foreach ($libros as $libro) {

        echo '<tr>';

        printf(
            '<td>%s</td>',
            htmlspecialchars(
                $libro['titulo'],
                ENT_QUOTES | ENT_SUBSTITUTE,
                'UTF-8'
            )
        );

        echo '</tr>';
    }

    echo '</table>';

} else {

    echo 'Non hai datos que amosar.';

}

?>

</body>
</html>
  • Neste caso empregamos `query()` porque a sentenza SQL non recibe ningún dato externo.
  • `fetchAll()` recupera todas as filas do resultado.
Esta solución é adecuada cando sabemos que o número de filas que imos recuperar é pequeno.
  • Se o resultado puidese ter moitas filas, sería preferible procesalas unha a unha:
while ($libro = $stmt->fetch()) {

echo htmlspecialchars(
    $libro['titulo'],
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);

}
  • IMPORTANTE: Non empregaremos `rowCount()` para comprobar cantas filas devolveu unha sentenza `SELECT`.
Se realmente necesitamos coñecer o número de rexistros, empregaremos `COUNT(*)`.


Caso Práctico 2

  • Imos recuperar todos os autores ordenados polos seus apelidos e nome.
  • Cada autor aparecerá como un enlace co seguinte formato:
 <a href="detalle_autor.php?id=5">
Apelidos, Nome </a>
  • O identificador será enviado mediante a URL.
Que un identificador apareza na URL non supón por si mesmo un problema de seguridade.
O importante é validar sempre o valor recibido na páxina de destino e comprobar que o usuario ten permiso para acceder ao recurso correspondente.

Arquivo: bd.php

define(
'CONSULTAR_AUTORES',
'SELECT id, apelidos, nome
FROM autores
ORDER BY apelidos, nome'
);

Solución Caso Práctico 2 empregando PDO

Arquivo: consultar_autores.php

<?php

ini_set('display_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,
        [
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]
    );

    $stmt = $conex->query(CONSULTAR_AUTORES);

    $autores = $stmt->fetchAll();

} catch (PDOException $e) {

    error_log($e->getMessage());
    die('Erro relacionado coa base de datos.');

} catch (Throwable $e) {

    die('Produciuse un erro na aplicación.');

}

?>

<!DOCTYPE html>

<html lang="gl">
<head>
    <meta charset="UTF-8">
    <title>Autores</title>
</head>

<body>

<?php

if ($autores) {

    echo '<ul>';

    foreach ($autores as $autor) {

        printf(
            "<li><a href='detalle_autor.php?id=%d'>%s, %s</a></li>",
            (int) $autor['id'],
            htmlspecialchars(
                $autor['apelidos'],
                ENT_QUOTES | ENT_SUBSTITUTE,
                'UTF-8'
            ),
            htmlspecialchars(
                $autor['nome'],
                ENT_QUOTES | ENT_SUBSTITUTE,
                'UTF-8'
            )
        );
    }

    echo '</ul>';

} else {

    echo 'Non hai datos que amosar.';

}

?>

</body>
</html>
  • Para amosar texto dentro de HTML empregamos `htmlspecialchars()`.
  • Non debemos empregar `urlencode()` sobre o nome e os apelidos, xa que estes valores non forman parte da URL senón do texto visible da páxina.


Consulta enviando parámetros de tipo numérico

  • Cando unha consulta necesita recibir datos externos empregaremos unha sentenza preparada mediante:
prepare()
execute()
  • Por exemplo:
SELECT col1, col2
FROM TABOA_1
WHERE col1 = :valor
  • Definimos a consulta:
define(
'CONSULTA_CON_PARAM_NUMERICO',
'SELECT col1, col2
FROM TABOA_1
WHERE col1 = :valor'
);
  • Podemos executala desta forma:
$stmt = $conex->prepare(
CONSULTA_CON_PARAM_NUMERICO
);

$stmt->execute([
'valor' => 5
]);
  • O nome do parámetro debe coincidir co indicado na sentenza SQL.
Se a consulta emprega:
:valor
debemos empregar:
$stmt->execute([
'valor' => 5
]);

Indicar explicitamente o tipo de dato

  • Cando enviamos os valores directamente mediante o array de `execute()`, PDO trátaos por defecto como `PDO::PARAM_STR`.
Na maioría das consultas MySQL realizará as conversións necesarias segundo o contexto.
  • Se queremos indicar explicitamente o tipo empregaremos `bindValue()`:
$stmt = $conex->prepare(
CONSULTA_CON_PARAM_NUMERICO
);

$stmt->bindValue(
':valor',
5,
PDO::PARAM_INT
);

$stmt->execute();
  • Algúns dos tipos dispoñibles son:
PDO::PARAM_INT
PDO::PARAM_STR
PDO::PARAM_BOOL
PDO::PARAM_NULL
  • É especialmente recomendable indicar explicitamente o tipo cando este teña importancia para a consulta, por exemplo con `LIMIT` ou `OFFSET`.


Caso práctico: Enviando parámetros de tipo numérico

  • Imos facer que o usuario poida seleccionar un libro nunha lista `<select>` e que se amosen os exemplares correspondentes.
Terás que programar que o libro se manteña seleccionado cando cambiemos de libro na lista. Para iso recorda que tes o libro selecccionado e cando creas a lista en html terás que comprobar si e o libro seleccionado, dentro do bucle que xera as etiquetas <option>, coincide coa etiqueta option seleccionada e se é o caso, poñela 'selected'.
Php BD 9.jpg



  • Primeiro definimos as consultas.

Arquivo: bd.php

define(
'CONSULTAR_LIBROS',
'SELECT id, titulo
FROM libros
ORDER BY titulo'
);

define(
'CONSULTAR_EXEMPLARES_POR_LIBRO',
'SELECT libro_id,
id_exemplar,
imaxe,
prezo,
estado_id
FROM exemplares
WHERE libro_id = :libro_id
ORDER BY estado_id'
);

define(
'CARTAFOL_IMAXES_WEB',
'http://localhost/IMAXES/'
);
Solución empregando PDO
Fixarse que


Arquivo: consultar_exemplares_por_libro.php

<?php

ini_set('display_errors', '1');
error_reporting(E_ALL);

require '/var/www/confBD/bd.php';

$libros = [];
$exemplares = [];

$libroSeleccionado = filter_input(
    INPUT_POST,
    'lstLibros',
    FILTER_VALIDATE_INT
);

try {

    $dsn = sprintf(
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
        SERVIDOR,
        BD
    );

    $conex = new PDO(
        $dsn,
        USUARIO,
        PASSWORD,
        [
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]
    );

    /*
     * Obtemos os libros que se van amosar
     * no elemento select.
     */
    $stmt = $conex->query(CONSULTAR_LIBROS);

    $libros = $stmt->fetchAll();


    /*
     * Se o usuario seleccionou un libro válido,
     * consultamos os seus exemplares.
     */
    if (
        $libroSeleccionado !== false &&
        $libroSeleccionado !== null &&
        $libroSeleccionado !== -1
    ) {

        $stmt = $conex->prepare(
            CONSULTAR_EXEMPLARES_POR_LIBRO
        );

        $stmt->bindValue(
            ':libro_id',
            $libroSeleccionado,
            PDO::PARAM_INT
        );

        $stmt->execute();

        $exemplares = $stmt->fetchAll();
    }

} catch (PDOException $e) {

    error_log($e->getMessage());
    die('Erro relacionado coa base de datos.');

} catch (Throwable $e) {

    die('Produciuse un erro na aplicación.');

}

?>

<!DOCTYPE html>

<html lang="gl">
<head>
    <meta charset="UTF-8">
    <title>Exemplares por libro</title>
</head>

<body>

<form method="post">

<select
    name="lstLibros"
    onchange="this.form.submit()"
>

    <option value="-1">
        -- Escolle un libro --
    </option>

    <?php foreach ($libros as $libro): ?>

        <option
            value="<?= (int) $libro['id'] ?>"
            <?php
            if (
                $libroSeleccionado !== false &&
                $libroSeleccionado !== null &&
                (int) $libro['id'] === $libroSeleccionado
            ) {
                echo 'selected';
            }
            ?>
        >
            <?= htmlspecialchars(
                $libro['titulo'],
                ENT_QUOTES | ENT_SUBSTITUTE,
                'UTF-8'
            ) ?>
        </option>

    <?php endforeach; ?>

</select>

</form>

<?php if (
    $libroSeleccionado !== false &&
    $libroSeleccionado !== null &&
    $libroSeleccionado !== -1
): ?>

<?php if ($exemplares): ?>

    <table border="1">

        <tr>
            <th>Num. exemplar</th>
            <th>Imaxe</th>
            <th>Prezo</th>
            <th>Estado</th>
        </tr>

        <?php foreach ($exemplares as $exemplar): ?>

            <tr>

                <td>
                    <?= (int) $exemplar['id_exemplar'] ?>
                </td>

                <td>
                    <img
                        src="<?= htmlspecialchars(
                            CARTAFOL_IMAXES_WEB .
                            ($exemplar['imaxe'] ?? ''),
                            ENT_QUOTES | ENT_SUBSTITUTE,
                            'UTF-8'
                        ) ?>"
                        alt="<?= htmlspecialchars(
                            $exemplar['imaxe'] ?? '',
                            ENT_QUOTES | ENT_SUBSTITUTE,
                            'UTF-8'
                        ) ?>"
                        width="42"
                        height="42"
                    >
                </td>

                <td>
                    <?= number_format(
                        (float) $exemplar['prezo'],
                        2,
                        ',',
                        '.'
                    ) ?>
                </td>

                <td>
                    <?= (int) $exemplar['estado_id'] ?>
                </td>

            </tr>

        <?php endforeach; ?>

    </table>

<?php else: ?>

    <p>Non hai exemplares que amosar.</p>

<?php endif; ?>

<?php endif; ?>

</body>
</html>
  • Neste exemplo empregamos `filter_input()` con `FILTER_VALIDATE_INT` para comprobar que o dato recibido é un número enteiro válido.
  • Despois utilizamos unha sentenza preparada e:
PDO::PARAM_INT
para indicar explicitamente que o identificador é numérico.
  • Non necesitamos empregar `mysqli_real_escape_string()` nin concatenar o dato dentro da consulta.

Amosar a descrición do estado

  • Podemos mellorar a consulta realizando un `JOIN` coa táboa `estados` para amosar o estado do exemplar.
Php BD 12.jpg


Arquivo: bd.php

define(
'CONSULTAR_EXEMPLARES_POR_LIBRO',
'SELECT exemplares.libro_id,
exemplares.id_exemplar,
exemplares.imaxe,
exemplares.prezo,
estados.descripcion AS desc_estado
FROM exemplares
INNER JOIN estados
ON estados.id = exemplares.estado_id
WHERE exemplares.libro_id = :libro_id
ORDER BY desc_estado'
);
  • Para amosar o estado:
echo htmlspecialchars(
$exemplar['desc_estado'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);


Exercicio proposto

  • Crea unha páxina chamada `consultar_exemplares_por_estado.php`.
  • Debe:
  • Amosar os estados da táboa `ESTADOS` mediante botóns de opción (`radio`).
  • Ao seleccionar un estado, realizarase automaticamente o envío do formulario.
  • O estado seleccionado debe manterse marcado.
  • Amosaranse os exemplares correspondentes ao estado seleccionado.
  • A consulta deberá realizarse mediante PDO e unha sentenza preparada.
  • O identificador do estado deberá validarse como número enteiro.
  • Empregarase `PDO::PARAM_INT` para enviar o parámetro.


Php BD 13.jpg
Nota: Podedes darlle un formato máis 'bonito' con CSS.


  • Consultas propostas:
define(
'CONSULTAR_ESTADOS',
'SELECT id, descripcion
FROM estados
ORDER BY descripcion'
);

define(
'CONSULTAR_EXEMPLARES_POR_ESTADO',
'SELECT libros.titulo,
exemplares.id_exemplar,
exemplares.prezo
FROM exemplares
INNER JOIN libros
ON libros.id = exemplares.libro_id
WHERE exemplares.estado_id = :estado_id
ORDER BY libros.titulo'
);


Caso práctico: Enviando parámetros de tipo cadea

  • Imos crear unha páxina que permita buscar libros polo nome dun autor.
O que se pretende programar é algo parecido a isto:
Php BD 11.jpg


O usuario introducirá nunha caixa de texto o nome, os apelidos ou unha parte deles.
Por exemplo, se introduce:
gar
poderían atoparse autores como `García`, `Vargas`, etc., dependendo dos datos existentes na base de datos.
  • Para realizar a busca empregaremos:
  • Un formulario HTML con método `POST`.
  • Unha consulta SQL co operador `LIKE`.
  • Unha sentenza preparada PDO para enviar o texto introducido polo usuario.
  • O parámetro `PDO::PARAM_STR`, xa que o dato enviado é unha cadea.
  • `fetchAll()` para recuperar os resultados.
  • `htmlspecialchars()` cando amosemos datos dentro do HTML.
  • A consulta devolverá:
  • O nome completo do autor.
  • O identificador do libro.
  • O título do libro.
  • Como un mesmo autor pode ter varios libros, evitaremos repetir o seu nome en todas as filas da táboa.
  • Ademais, o título de cada libro será un enlace que permitirá abrir unha nova xanela coa páxina:
detalle_libro.php
á que enviaremos o identificador do libro mediante `GET`.
Para abrir esa páxina nunha nova xanela empregaremos JavaScript e a función `window.open()`.
Polo tanto, o funcionamento xeral será:
Usuario introduce o nome do autor
↓
Validamos o texto
↓
prepare() + bindValue()
↓
Consulta con LIKE
↓
Recuperamos os libros
↓
Amosamos autor e título
↓
Preme sobre un título
↓
window.open()
↓
detalle_libro.php?libro=ID
Arquivo bd.php
  • Engadimos a consulta que permite buscar libros polo nome ou apelidos dun autor:
define(
'CONSULTAR_LIBROS_POR_AUTOR',
"SELECT
CONCAT(
autores.apelidos,
', ',
autores.nome
) AS nome_autor,
libros.id AS id_libro,
libros.titulo
FROM libros
INNER JOIN libro_autor
ON libro_autor.libro_id = libros.id
INNER JOIN autores
ON libro_autor.autor_id = autores.id
WHERE CONCAT(
autores.nome,
' ',
autores.apelidos
) LIKE :autor
ORDER BY nome_autor, libros.titulo"
);
  • A consulta une as táboas:
libros
libro_autor
autores
para obter os libros correspondentes a cada autor.
  • Mediante:
CONCAT(
autores.nome,
' ',
autores.apelidos
)
construímos unha cadea formada polo nome e os apelidos do autor.
  • Sobre esa cadea aplicamos:
LIKE :autor
sendo `:autor` o parámetro que enviaremos posteriormente desde PDO.
  • O parámetro debe representar o valor completo.
Polo tanto, non escribiremos:
LIKE '%:autor%'
senón que os caracteres `%` serán engadidos ao valor desde PHP:
'%' . $textoBuscar . '%'
Solución completa

Arquivo: buscar_libros_por_autor.php

<?php

/*
 * Estas directivas utilízanse durante o desenvolvemento.
 * Nun servidor en produción non se deberían amosar
 * os erros PHP ao usuario.
 */
ini_set('display_errors', '1');
error_reporting(E_ALL);

require '/var/www/confBD/bd.php';


/*
 * Variables que utilizaremos posteriormente no HTML.
 */
$erro = '';
$libros = [];


/*
 * Recuperamos o valor enviado polo formulario.
 *
 * O operador ?? permite indicar unha cadea baleira
 * se o formulario aínda non foi enviado.
 *
 * trim() elimina espazos en branco ao principio
 * e ao final da cadea.
 */
$textoBuscar = trim(
    $_POST['txtAutor'] ?? ''
);


try {

    /*
     * Creamos o DSN.
     */
    $dsn = sprintf(
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
        SERVIDOR,
        BD
    );


    /*
     * Abrimos a conexión.
     *
     * Os datos SERVIDOR, USUARIO, PASSWORD e BD
     * están definidos no arquivo bd.php.
     */
    $conex = new PDO(
        $dsn,
        USUARIO,
        PASSWORD,
        [
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]
    );


    /*
     * Comprobamos se o formulario foi enviado.
     */
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {

        /*
         * Comprobamos que se introduciu algún texto.
         */
        if ($textoBuscar === '') {

            $erro = 'Debes introducir un texto para realizar a busca.';

            /*
         * Como regra da aplicación esiximos
         * polo menos 3 caracteres.
         */
        } elseif (mb_strlen($textoBuscar) < 3) {

            $erro =
                'A cadea a buscar debe ter polo menos 3 caracteres.';
        } else {

            /*
             * Preparamos a consulta.
             */
            $stmt = $conex->prepare(
                CONSULTAR_LIBROS_POR_AUTOR
            );


            /*
             * Engadimos os comodíns % de LIKE ao valor
             * que enviamos ao parámetro.
             *
             * Por exemplo:
             *
             * gar
             *
             * convértese en:
             *
             * %gar%
             */
            $stmt->bindValue(
                ':autor',
                '%' . $textoBuscar . '%',
                PDO::PARAM_STR
            );


            /*
             * Executamos a consulta.
             */
            $stmt->execute();


            /*
             * Recuperamos todos os resultados.
             */
            $libros = $stmt->fetchAll();


            /*
             * Se non hai filas gardamos unha mensaxe
             * para amosala posteriormente.
             */
            if (!$libros) {

                $erro =
                    'Non se atoparon libros para ese autor.';
            }
        }
    }
} catch (PDOException $e) {

    /*
     * Rexistramos a información técnica no log.
     * Non debemos amosala ao usuario.
     */
    error_log(
        $e->getMessage()
    );

    die('Erro relacionado coa base de datos.');
} catch (Throwable $e) {

    die('Produciuse un erro na aplicación.');
}

?>

<!DOCTYPE html>

<html lang="gl">

    <head>

        <meta charset="UTF-8">
        <title>Buscar libros por autor</title>
        <script>
            /*
            * Recibe a URL que queremos abrir
            * e solicita ao navegador unha nova xanela.
            */
            function abrirVentana(url) {

                window.open(
                    url,
                    '_blank',
                    'popup,width=600,height=400,noopener'
                );
            }
        </script>
    </head>

<body>

    <h1>Buscar libros por autor</h1>
    <form method="post">

        <label for="txtAutor">
            Introduce o nome ou apelidos do autor:
        </label>
        <input
            type="text"
            id="txtAutor"
            name="txtAutor"
            size="50"
            maxlength="100"
            required
            value="<?= htmlspecialchars(
                        $textoBuscar,
                        ENT_QUOTES | ENT_SUBSTITUTE,
                        'UTF-8'
                    ) ?>">
        <button
            type="submit"
            value="BUSCAR">Buscar</button>
        <input
            type="reset"
            value="LIMPAR">

    </form>

    <?php if ($erro !== ''): ?>
        <p>

            <?= htmlspecialchars(
                $erro,
                ENT_QUOTES | ENT_SUBSTITUTE,
                'UTF-8'
            ) ?>
        </p>
    <?php endif; ?>

    <?php if ($libros): ?>
        <table border="1">
            <tr>
                <th>Autor</th>
                <th>Libro</th>
            </tr>
            <?php

         /*
         * Esta variable permite saber cal foi
         * o último autor que amosamos.
         */
            $ultimoAutor = '';
            foreach ($libros as $libro):
             /*
             * Se o autor desta fila é o mesmo
             * que o da fila anterior,
             * non volvemos amosar o seu nome.
             */
                if (
                    $ultimoAutor ===
                    $libro['nome_autor']
                ) {

                    $autorAmosar = '';
                } else {

                    $autorAmosar =
                        $libro['nome_autor'];
                }

            ?>
                <tr>
                    <td>
                        <?= htmlspecialchars(
                            $autorAmosar,
                            ENT_QUOTES | ENT_SUBSTITUTE,
                            'UTF-8'
                        ) ?>
                    </td>
                    <td>
                        <a
                            href="detalle_libro.php?libro=<?= (int) $libro['id_libro'] ?>"
                            onclick="abrirVentana(this.href); return false;">

                            <?= htmlspecialchars(
                                $libro['titulo'],
                                ENT_QUOTES | ENT_SUBSTITUTE,
                                'UTF-8'
                            ) ?>
                        </a>
                    </td>
                </tr>
            <?php

             /*
             * Gardamos o autor actual para comparalo
             * co autor da seguinte fila.
             */
                $ultimoAutor =
                    $libro['nome_autor'];

            endforeach;
            ?>
        </table>
    <?php endif; ?>
</body>
</html>
Explicación da solución
  • Recuperamos o texto enviado polo formulario mediante:
$textoBuscar = trim(
$_POST['txtAutor'] ?? ''
);
  • Non empregamos `htmlspecialchars()` neste momento.
Neste punto estamos validando e procesando un dato de entrada.
`htmlspecialchars()` empregarase posteriormente cando o dato se introduza dentro do HTML.



Validación do dato

  • Comprobamos primeiro que a cadea non estea baleira:
if ($textoBuscar === '') {
  • Como exemplo dunha regra de validación da aplicación, esiximos tamén unha lonxitude mínima:
elseif (mb_strlen($textoBuscar) < 3) {
A esixencia de tres caracteres é unha regra da nosa aplicación.
Non é unha medida para evitar inxección SQL.



Preparación da consulta

  • A consulta recibe datos procedentes do usuario, polo que debemos empregar unha sentenza preparada:
$stmt = $conex->prepare(
CONSULTAR_LIBROS_POR_AUTOR
);



Asociación do parámetro

  • Asociamos o valor ao parámetro `:autor`:
$stmt->bindValue(
':autor',
'%' . $textoBuscar . '%',
PDO::PARAM_STR
);
  • Indicamos:
PDO::PARAM_STR
porque estamos enviando unha cadea.



Funcionamento de LIKE

  • Os caracteres `%` actúan como comodíns.
Se o usuario introduce:
gar
PHP enviará:
%gar%
  • Desta forma poderán coincidir cadeas nas que `gar` apareza ao principio, no medio ou ao final.



Execución e recuperación dos resultados

  • Executamos:
$stmt->execute();
  • E recuperamos os resultados:
$libros = $stmt->fetchAll();
  • Como na conexión configuramos:
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
cada fila recuperada será un array asociativo.



Evitar repetir o nome do autor

  • Pode suceder que un mesmo autor teña varios libros.
Por exemplo:
García, Ana | Libro A
García, Ana | Libro B
García, Ana | Libro C
Pérez, Luís | Libro D
  • Queremos amosalo desta forma:
García, Ana | Libro A
            | Libro B
            | Libro C
Pérez, Luís | Libro D
  • Para iso gardamos o último autor procesado:
$ultimoAutor = '';
  • En cada iteración comparamos:
if ($ultimoAutor ===$libro['nome_autor']) {

   $autorAmosar = '';

} else {
    $autorAmosar = $libro['nome_autor'];
}
  • E ao rematar gardamos o autor actual:
$ultimoAutor =
$libro['nome_autor'];



Abrir o detalle dun libro

  • Cada título será un enlace á páxina:
detalle_libro.php
  • O identificador enviarase mediante `GET`:
detalle_libro.php?libro=5


  • O enlace xerado será:
<a href="detalle_libro.php?libro=<?= (int) $libro['id_libro'] ?>" onclick="abrirVentana(this.href); return false;">
  • Mediante:
this.href
obtemos a URL real almacenada no atributo `href`.
  • Chamamos despois á función:
abrirVentana(this.href);
  • Finalmente:
return false;
evita que o navegador abra tamén o enlace na xanela actual.



Función JavaScript

  • A función empregada é:
function abrirVentana(url) {

 window.open(
    url,
    '_blank',
    'popup,width=600,height=400,noopener'
 );

}
  • `window.open()` solicita ao navegador a apertura dunha nova xanela.
  • Indicamos aproximadamente:
width=600
height=400
  • A opción:
noopener
evita que a nova xanela poida acceder á xanela orixinal mediante `window.opener`.

NOTA: O navegador ten a decisión final sobre se a páxina se abre nunha nova xanela ou nunha pestana e pode ignorar algunhas características solicitadas.



Vantaxe de empregar un href real

  • Non empregamos:
 <a href="#">
  • Empregamos:
 <a href="detalle_libro.php?libro=5">
Desta forma, se JavaScript non funciona ou está desactivado, o enlace segue sendo válido e o usuario poderá acceder igualmente ao detalle do libro.



Páxina detalle_libro.php

  • A páxina de detalle recibirá o identificador do libro mediante:
$_GET['libro']
  • Como é un dato externo, debemos validalo:
$libroId = filter_input(
INPUT_GET,
'libro',
FILTER_VALIDATE_INT
);

if (
  $libroId === false ||
  $libroId === null ||
  $libroId < 1
) {

  die('Identificador de libro non válido.');

}


  • A consulta posterior tamén deberá ser preparada:
$stmt = $conex->prepare(
  'SELECT id, titulo
   FROM libros
   WHERE id = :libro_id'
);

$stmt->bindValue(':libro_id',$libroId,PDO::PARAM_INT);
$stmt->execute();

$libro = $stmt->fetch();
  • Se queremos recuperar tamén os exemplares dese libro faremos outra consulta preparada:
$stmt = $conex->prepare(
 'SELECT id_exemplar,imaxe,prezo,estado_id
  FROM exemplares
  WHERE libro_id = :libro_id
  ORDER BY id_exemplar'
);

$stmt->bindValue(':libro_id',$libroId,PDO::PARAM_INT);
$stmt->execute();

$exemplares = $stmt->fetchAll();
Resumo do proceso
$_POST['txtAutor']
↓
trim()
↓
validación
↓
prepare()
↓
bindValue(PDO::PARAM_STR)
↓
execute()
↓
fetchAll()
↓
htmlspecialchars()
↓
enlace ao libro
↓
window.open()
↓
detalle_libro.php?libro=ID
↓
FILTER_VALIDATE_INT
↓
prepare() + PDO::PARAM_INT

</syntaxhighlight>


  • Debemos diferenciar claramente:
! Operación

| ! Finalidade                                           |
| ------------------------------------------------------ |
| `trim()` e comprobación da lonxitude                   |
| Validar os datos segundo as regras da aplicación.      |
| -                                                      |
| `prepare()` + `bindValue()`                            |
| Separar os datos da estrutura da sentenza SQL.         |
| -                                                      |
| `PDO::PARAM_STR`                                       |
| Indicar que o parámetro enviado é unha cadea.          |
| -                                                      |
| `htmlspecialchars()`                                   |
| Escapar datos cando se introducen dentro de HTML.      |
| -                                                      |
| `window.open()`                                        |
| Abrir o detalle do libro nunha nova xanela ou pestana. |
| -                                                      |
| `FILTER_VALIDATE_INT`                                  |
| Validar o identificador recibido por `GET`.            |
| }                                                      |



Consulta chamando a procedementos almacenados

  • Un procedemento almacenado permite gardar no propio xestor de base de datos un conxunto de sentenzas SQL que poden ser executadas posteriormente.
  • Desde PDO chamarémolo mediante a sentenza:
CALL NOME_PROCEDEMENTO()
  • Por exemplo, en MySQL:
DELIMITER $$

CREATE PROCEDURE CONSULTAR_LIBROS()
BEGIN

SELECT id, titulo
FROM libros
ORDER BY titulo;

END$$

DELIMITER ;

Empregando PDO

$stmt = $conex->prepare(
'CALL CONSULTAR_LIBROS()'
);

$stmt->execute();

$libros = $stmt->fetchAll();

$stmt->closeCursor();
  • `closeCursor()` libera os resultados pendentes asociados á chamada.
É especialmente recomendable ao traballar con procedementos almacenados en MySQL antes de executar outra consulta na mesma conexión.

Caso Práctico: Consulta chamando a procedementos almacenados

  • Definimos o nome do procedemento:
define(
'PROCEDEMENTO_CONSULTAR_LIBROS',
'CONSULTAR_LIBROS'
);
  • Realizamos a chamada:
$stmt = $conex->prepare(
'CALL ' .
PROCEDEMENTO_CONSULTAR_LIBROS .
'()'
);

$stmt->execute();

$libros = $stmt->fetchAll();

$stmt->closeCursor();
  • Non necesitamos comprobar o resultado de `execute()` para saber se o `SELECT` devolveu filas.
`execute()` indica se a sentenza puido executarse.
Para saber se temos resultados comprobamos os datos recuperados:
if ($libros) {


// Hai resultados

} else {

// Non hai resultados

}


Consulta chamando a procedementos almacenados con parámetros de entrada

  • Os parámetros dun procedemento almacenado poden ser:
  • `IN`: parámetros de entrada.
  • `OUT`: parámetros de saída.
  • `INOUT`: funcionan como entrada e saída.
  • Por exemplo:
DELIMITER $$

CREATE PROCEDURE PROCEDEMENTO_CON_PARAMETROS(
IN p_num INT,
IN p_text VARCHAR(100)
)
BEGIN

SELECT col1, col2
FROM TABOA_1
WHERE col1 = p_num
  AND col2 = p_text;

END$$

DELIMITER ;

Empregando PDO

  • Preparamos a chamada igual que calquera outra sentenza parametrizada:
$stmt = $conex->prepare(
'CALL PROCEDEMENTO_CON_PARAMETROS(
:param_num,
:param_text
)'
);

$stmt->bindValue(
':param_num',
$valorNum,
PDO::PARAM_INT
);

$stmt->bindValue(
':param_text',
$valorTexto,
PDO::PARAM_STR
);

$stmt->execute();

$filas = $stmt->fetchAll();

$stmt->closeCursor();
  • Desta forma non necesitamos empregar `sprintf()` nin concatenar os valores dentro da chamada SQL.

Caso Práctico: buscar libros por autor

  • Lembrar que o usuario que se conecta á base de datos dende php ten que ter permiso execute sobre a base de datos.
 grant execute on php.* to 'user_php'@'localhost';
 flush privileges;


  • Procedemento almacenado (isto tedes que executalo en Mysql empregando a ferramenta Mysql WorkBench ou PhpMyAdmin):
DELIMITER $$

CREATE PROCEDURE CONSULTAR_LIBROS_POR_AUTOR(
IN p_autor_buscar VARCHAR(100)
)
BEGIN

SELECT CONCAT(
           autores.apelidos,
           ', ',
           autores.nome
       ) AS nome_autor,
       libros.id AS id_libro,
       libros.titulo
FROM libros
INNER JOIN libro_autor
    ON libro_autor.libro_id = libros.id
INNER JOIN autores
    ON libro_autor.autor_id = autores.id
WHERE CONCAT(
          autores.nome,
          ' ',
          autores.apelidos
      ) LIKE CONCAT(
          '%',
          p_autor_buscar,
          '%'
      )
ORDER BY nome_autor, libros.titulo;

END$$

DELIMITER ;
  • Chamada desde PDO cambiando no exercicio anterior a chamada ao SELECT pola chamada a este procedemento almacenado:
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {

        /*
         * Comprobamos que se introduciu algún texto.
         */
        if ($textoBuscar === '') {

            $erro = 'Debes introducir un texto para realizar a busca.';

            /*
         * Como regra da aplicación esiximos
         * polo menos 3 caracteres.
         */
        } elseif (mb_strlen($textoBuscar) < 3) {

            $erro =
                'A cadea a buscar debe ter polo menos 3 caracteres.';
        } else {

            /*
             * Preparamos a consulta.
             */
            $stmt = $conex->prepare(
                'call CONSULTAR_LIBROS_POR_AUTOR(:autor)'
            );


            /*
             * Engadimos os comodíns % de LIKE ao valor
             * que enviamos ao parámetro.
             *
             * Por exemplo:
             *
             * gar
             *
             * convértese en:
             *
             * %gar%
             */
            $stmt->bindValue(
                ':autor',
                '%' . $textoBuscar . '%',
                PDO::PARAM_STR
            );


            /*
             * Executamos a consulta.
             */
            $stmt->execute();


            /*
             * Recuperamos todos os resultados.
             */
            $libros = $stmt->fetchAll();

            $stmt->closeCursor();


            /*
             * Se non hai filas gardamos unha mensaxe
             * para amosala posteriormente.
             */
            if (!$libros) {

                $erro =
                    'Non se atoparon libros para ese autor.';
            }
        }
    }


Consulta chamando a procedementos almacenados con parámetros de saída

  • Un parámetro `OUT` permite que un procedemento devolva un valor.
  • Por exemplo:
DELIMITER $$

CREATE PROCEDURE CONSULTAR_NUM_LIBROS(
OUT p_num_libros INT
)
BEGIN

SELECT COUNT(*)
INTO p_num_libros
FROM libros;

END$$

DELIMITER ;
  • Con PDO e MySQL podemos empregar unha variable de usuario de MySQL:
$stmt = $conex->prepare(
'CALL CONSULTAR_NUM_LIBROS(
@numero_libros
)'
);

$stmt->execute();

/*

* É importante liberar os resultados
* da chamada antes de executar outro SELECT.
  */
  $stmt->closeCursor();

$stmt = $conex->query(
'SELECT @numero_libros'
);

$numLibros = (int) $stmt->fetchColumn();
  • Podemos amosar o resultado:
printf(
'<h1>Número de libros á venda: %d</h1>',
$numLibros
);


Consulta chamando a funcións

  • MySQL tamén permite definir funcións almacenadas.
  • Unha función, a diferenza dun procedemento, devolve directamente un valor e pode empregarse dentro dunha expresión SQL.
  • Por exemplo:
DELIMITER $$

CREATE FUNCTION CONSULTAR_NUM_LIBROS()
RETURNS INT
READS SQL DATA
BEGIN

DECLARE num_libros INT;

SELECT COUNT(*)
INTO num_libros
FROM libros;

RETURN num_libros;

END$$

DELIMITER ;

Empregando PDO

  • Podemos chamar á función mediante un `SELECT`:
$stmt = $conex->query(
'SELECT CONSULTAR_NUM_LIBROS()'
);

$numLibros = (int) $stmt->fetchColumn();
  • `fetchColumn()` resulta especialmente útil cando unha consulta devolve un único valor.

Funcións con parámetros

  • Supoñamos unha función:
SELECT Suma(5, 7)
  • Desde PDO:
$stmt = $conex->prepare(
'SELECT Suma(:a, :b)'
);

$stmt->bindValue(
':a',
5,
PDO::PARAM_INT
);

$stmt->bindValue(
':b',
7,
PDO::PARAM_INT
);

$stmt->execute();

$resultado = $stmt->fetchColumn();


Paxinación de resultados

  • En moitas ocasións unha consulta pode devolver unha cantidade elevada de rexistros.
Para evitar amosalos todos simultaneamente podemos dividir o resultado en páxinas.
  • En MySQL podemos empregar `LIMIT` e `OFFSET`.
  • Por exemplo:
SELECT id, titulo
FROM libros
ORDER BY titulo
LIMIT 2 OFFSET 0;
Devolverá os dous primeiros rexistros.
SELECT id, titulo
FROM libros
ORDER BY titulo
LIMIT 2 OFFSET 2;
Devolverá os dous seguintes.

Pasos necesarios para realizar a paxinación

  • Necesitamos:
  1. Coñecer o número total de rexistros.
  2. Establecer cantos rexistros se amosarán por páxina.
  3. Coñecer a páxina actual.
  4. Calcular o número total de páxinas.
  5. Calcular o desprazamento (`OFFSET`).
  6. Realizar a consulta correspondente.



Número total de rexistros

$stmt = $conex->query(
'SELECT COUNT(*)
FROM libros'
);

$numLibros = (int) $stmt->fetchColumn();



Número de rexistros por páxina

define(
'CONST_NUM_LIBROS_PAXINA',
10
);



Obter e validar a páxina actual

  • O número de páxina chegará normalmente mediante `GET`.
$paxinaActual = filter_input(
INPUT_GET,
'paxina',
FILTER_VALIDATE_INT
);

if (
$paxinaActual === false ||
$paxinaActual === null ||
$paxinaActual < 1
) {

$paxinaActual = 1;

}
  • `htmlspecialchars()` non serve para validar que un dato sexa numérico.
Neste caso empregamos `FILTER_VALIDATE_INT`.



Número total de páxinas

$totalPaxinas = (int) ceil(
$numLibros /
CONST_NUM_LIBROS_PAXINA
);
  • Podemos evitar que o usuario solicite unha páxina superior á existente:
if (
$totalPaxinas > 0 &&
$paxinaActual > $totalPaxinas
) {

$paxinaActual = $totalPaxinas;

}



Calcular o OFFSET

$inicio = (
$paxinaActual - 1
) * CONST_NUM_LIBROS_PAXINA;



Consulta paxinada

  • Como `LIMIT` e `OFFSET` necesitan valores numéricos, indicaremos explicitamente o tipo mediante `PDO::PARAM_INT`.
$stmt = $conex->prepare(
'SELECT id, titulo
FROM libros
ORDER BY titulo
LIMIT :limite
OFFSET :inicio'
);

$stmt->bindValue(
':limite',
CONST_NUM_LIBROS_PAXINA,
PDO::PARAM_INT
);

$stmt->bindValue(
':inicio',
$inicio,
PDO::PARAM_INT
);

$stmt->execute();

$libros = $stmt->fetchAll();



Amosar os resultados

if ($libros) {

echo "<table border='1'>";

foreach ($libros as $libro) {

    echo '<tr>';

    printf(
        '<td>%s</td>',
        htmlspecialchars(
            $libro['titulo'],
            ENT_QUOTES | ENT_SUBSTITUTE,
            'UTF-8'
        )
    );

    echo '</tr>';
}

echo '</table>';

} else {

echo 'Non hai datos que amosar.';

}



Amosar os enlaces ás páxinas

echo '<div>';

for (
$cont = 1;
$cont <= $totalPaxinas;
$cont++
) {

if ($cont === $paxinaActual) {

    printf(
        '<strong>%d</strong> ',
        $cont
    );

} else {

    printf(
        "<a href='?paxina=%d'>%d</a> ",
        $cont,
        $cont
    );
}

}

echo '</div>';


Exemplo completo de paxinación

Aquivo bd.php

define(
    'CONST_NUM_LIBROS_PAXINA',
    10
);
::


Páxina php a cargar

<?php

ini_set('display_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,
        [
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]
    );


    /*
     * Número total de libros.
     */
    $stmt = $conex->query(
        'SELECT COUNT(*)
         FROM libros'
    );

    $numLibros = (int) $stmt->fetchColumn();


    /*
     * Número total de páxinas.
     */
    $totalPaxinas = (int) ceil(
        $numLibros /
        CONST_NUM_LIBROS_PAXINA
    );


    /*
     * Páxina solicitada polo usuario.
     */
    $paxinaActual = filter_input(
        INPUT_GET,
        'paxina',
        FILTER_VALIDATE_INT
    );

    if (
        $paxinaActual === false ||
        $paxinaActual === null ||
        $paxinaActual < 1
    ) {

        $paxinaActual = 1;
    }


    if (
        $totalPaxinas > 0 &&
        $paxinaActual > $totalPaxinas
    ) {

        $paxinaActual = $totalPaxinas;
    }


    /*
     * Primeiro rexistro da páxina.
     */
    $inicio = (
        $paxinaActual - 1
    ) * CONST_NUM_LIBROS_PAXINA;


    /*
     * Consulta dos libros da páxina actual.
     */
    $stmt = $conex->prepare(
        'SELECT id, titulo
         FROM libros
         ORDER BY titulo
         LIMIT :limite
         OFFSET :inicio'
    );

    $stmt->bindValue(
        ':limite',
        CONST_NUM_LIBROS_PAXINA,
        PDO::PARAM_INT
    );

    $stmt->bindValue(
        ':inicio',
        $inicio,
        PDO::PARAM_INT
    );

    $stmt->execute();

    $libros = $stmt->fetchAll();

} catch (PDOException $e) {

    error_log($e->getMessage());
    die('Erro relacionado coa base de datos.');

} catch (Throwable $e) {

    die('Produciuse un erro na aplicación.');

}

?>

<!DOCTYPE html>

<html lang="gl">

<head>
    <meta charset="UTF-8">
    <title>Paxinación de libros</title>
</head>

<body>

<?php if ($libros): ?>

<table border="1">

    <?php foreach ($libros as $libro): ?>

        <tr>
            <td>
                <?= htmlspecialchars(
                    $libro['titulo'],
                    ENT_QUOTES | ENT_SUBSTITUTE,
                    'UTF-8'
                ) ?>
            </td>
        </tr>

    <?php endforeach; ?>

</table>

<?php else: ?>

<p>Non hai libros que amosar.</p>

<?php endif; ?>

<?php if ($totalPaxinas > 1): ?>

<div>

    <?php
    for (
        $cont = 1;
        $cont <= $totalPaxinas;
        $cont++
    ):
    ?>

        <?php if ($cont === $paxinaActual): ?>

            <strong>
                <?= $cont ?>
            </strong>

        <?php else: ?>

            <a href="?paxina=<?= $cont ?>">
                <?= $cont ?>
            </a>

        <?php endif; ?>

    <?php endfor; ?>

</div>

<?php endif; ?>

</body>
</html>

Sobre a optimización da paxinación

  • En xeral non debemos enviar pola URL o número total de rexistros e confiar posteriormente nese dato:
?paxina=2&numtotal=50
Os datos enviados mediante `GET` poden ser modificados polo usuario.
  • Gardar o número total de rexistros nunha variable de sesión tampouco garante que o dato siga sendo correcto, xa que outros usuarios poderían inserir ou eliminar rexistros.
  • Como primeira solución realizaremos en cada petición:
SELECT COUNT(*)
FROM libros
  • Se nunha aplicación real esta operación chegase a ser un problema de rendemento poderían estudarse técnicas de caché ou outras estratexias de optimización.


Exercicio proposto

  • Crea unha páxina chamada `buscar_libros.php` que permita buscar libros empregando os seguintes criterios:
  • Título: unha caixa de texto que realizará unha busca mediante `LIKE`.
  • Prezo: un campo no que se validará que o valor introducido sexa numérico.
  • Estado: unha lista `<select>` cos estados dispoñibles.
  • Autor: unha lista `<select>` cos autores dispoñibles.
  • Criterio de ordenación: título, prezo ou autor.
  • O usuario poderá combinar os diferentes criterios.
  • Os resultados deberán poder amosarse paxinados.
  • A solución deberá:
  • Empregar PDO.
  • Empregar sentenzas preparadas para todos os datos externos.
  • Validar os valores recibidos mediante `GET` ou `POST`.
  • Empregar `bindValue()` co tipo correspondente cando sexa necesario.
  • Non concatenar directamente datos introducidos polo usuario dentro dunha sentenza SQL.
  • Empregar `htmlspecialchars()` cando se amosen cadeas dentro do HTML.
  • Capturar os erros PDO mediante `PDOException`.
  • Rexistrar os detalles do erro mediante `error_log()`.
  • Amosar ao usuario unha mensaxe xenérica en caso de erro.
  • Capturar outros erros da aplicación mediante `Throwable`.


Resumo das funcións PDO empregadas

! Método / constante

| ! Uso                                                                                             |
| ------------------------------------------------------------------------------------------------- |
| `new PDO(...)`                                                                                    |
| Abrir unha conexión coa base de datos.                                                            |
| -                                                                                                 |
| `query()`                                                                                         |
| Executar unha sentenza SQL sen parámetros externos e obter un resultado.                          |
| -                                                                                                 |
| `prepare()`                                                                                       |
| Preparar unha sentenza SQL parametrizada.                                                         |
| -                                                                                                 |
| `execute()`                                                                                       |
| Executar unha sentenza preparada.                                                                 |
| -                                                                                                 |
| `bindValue()`                                                                                     |
| Asociar un valor e, opcionalmente, o seu tipo a un parámetro.                                     |
| -                                                                                                 |
| `fetch()`                                                                                         |
| Recuperar unha fila do resultado.                                                                 |
| -                                                                                                 |
| `fetchAll()`                                                                                      |
| Recuperar todas as filas do resultado.                                                            |
| -                                                                                                 |
| `fetchColumn()`                                                                                   |
| Recuperar o valor dunha columna, especialmente útil para `COUNT(*)` ou funcións.                  |
| -                                                                                                 |
| `closeCursor()`                                                                                   |
| Liberar os resultados pendentes dunha sentenza, especialmente útil con procedementos almacenados. |
| -                                                                                                 |
| `PDO::PARAM_INT`                                                                                  |
| Indica un parámetro de tipo enteiro.                                                              |
| -                                                                                                 |
| `PDO::PARAM_STR`                                                                                  |
| Indica un parámetro de tipo cadea.                                                                |
| -                                                                                                 |
| `PDO::PARAM_BOOL`                                                                                 |
| Indica un parámetro booleano.                                                                     |
| -                                                                                                 |
| `PDO::PARAM_NULL`                                                                                 |
| Indica un valor nulo.                                                                             |
| -                                                                                                 |
| `PDO::FETCH_ASSOC`                                                                                |
| Recupera as filas como arrays asociativos.                                                        |
| }                                                                                                 |
::

Regras que empregaremos nos exames

  1. As operacións contra a base de datos realizaranse mediante PDO.
  1. Para consultas sen parámetros externos poderá empregarse `query()`.
  1. Se hai datos procedentes do usuario ou outros valores externos empregarase `prepare()` + `execute()`.
  1. Non se concatenarán directamente datos externos nas sentenzas SQL.
  1. Os datos recibidos mediante formularios ou URL deberán validarse.
  1. Cando o tipo sexa relevante empregarase `bindValue()` indicando `PDO::PARAM_INT`, `PDO::PARAM_STR`, etc.
  1. Non se empregará `rowCount()` para coñecer o número de filas devoltas por un `SELECT`.
  1. Se necesitamos contar filas empregaremos `COUNT(*)`.
  1. Para amosar cadeas dentro de HTML empregarase `htmlspecialchars()`.
  1. Os erros PDO capturaranse mediante `PDOException`.
  1. Os detalles técnicos dos erros rexistraranse mediante `error_log()` e non se amosarán ao usuario final.
  1. Os demais erros ou excepcións poderán capturarse mediante `Throwable`.
  1. Os exemplos realizaranse tomando como referencia PHP 8.5.






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