Esta página contiene comentarios ampliados con fragmentos de código PHP de los ejercicios Bases de datos (2).
Estos son los fragmentos de código que se deben añadir a las páginas del ejercicio Bases de datos (1) 6 para resolver el ejercicio Bases de datos (2) 1.
$pdo = conectaDb();
...
// $pdo = null; // No incluimos esta instrucción
// Número máximo de registros en las tablas
$cfg["tablaPersonasMaxReg"] = 20; // Número máximo de registros en la tabla Personas
Para ello, podemos hacer una consulta COUNT(*), que devuelve un único registro con una única columna que contiene el número de registros en la tabla. Podemos obtener ese valor con el método fetchColumn().
$consulta = "SELECT COUNT(*) FROM $cfg[tablaPersonas]";
$resultado = $pdo->query($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error en la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif ($resultado->fetchColumn() >= $cfg["tablaPersonasMaxReg"]) {
print " <p class=\"aviso\">Se ha alcanzado el número máximo de registros que se pueden guardar.</p>\n";
print "\n";
print " <p class=\"aviso\">Por favor, borre algún registro antes.</p>\n";
} else {
...
En la comprobación de los valores de $nombre y $apellidos podemos añadir una condición suplementaria:
if ($nombre == "" && $apellidos == "") {
print " <p class=\"aviso\">Hay que rellenar al menos uno de los campos. No se ha guardado el registro.</p>\n";
print "\n";
$nombreOk = $apellidosOk = false;
}
Esta consulta se debe realizar con sentencias preparadas ya que incluye datos proporcionados por el usuario.
$consulta = "SELECT COUNT(*) FROM $cfg[tablaPersonas]
WHERE nombre = :nombre
AND apellidos = :apellidos";
$resultado = $pdo->prepare($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute([":nombre" => $nombre, ":apellidos" => $apellidos])) {
print " <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif ($resultado->fetchColumn() > 0) {
print " <p class=\"aviso\">El registro ya existe.</p>\n";
} else {
...
Es conveniente hacer esta comprobación aunque se haya hecho ya en insertar-1.php, ya que otro usuario puede haber introducido registros mientras el primer usuario estaba rellenando el formulario de insertar-1.php.
$consulta = "SELECT COUNT(*) FROM $cfg[tablaPersonas]";
$resultado = $pdo->query($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error en la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif ($resultado->fetchColumn() >= $cfg["tablaPersonasMaxReg"]) {
print " <p class=\"aviso\">Se ha alcanzado el número máximo de registros que se pueden guardar.</p>\n";
print "\n";
print " <p class=\"aviso\">Por favor, borre algún registro antes de insertar un nuevo registro.</p>\n";
} else {
...
Para no tener que hacer una consulta contando los registros y otra recuperando los registros, utilizaremos el método ->fetchAll() y la función count().
$consulta = "SELECT * FROM $cfg[tablaPersonas]
ORDER BY $ordena";
$resultado = $pdo->query($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error en la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (count($registros = $resultado->fetchAll()) == 0) {
print " <p class=\"aviso\">No se ha creado todavía ningún registro.</p>\n";
} else {
...
Esta comprobación es la misma que la de listar.php
Como $id es una matriz, esta comprobación se puede hacer contando los elementos recibidos utilizando la función count().
$id = recoge("id", []);
if (count($id) == 0) {
print " <p class=\"aviso\">No se ha seleccionado ningún registro.</p>\n";
} else {
...
$consulta = "SELECT COUNT(*) FROM $cfg[tablaPersonas]
WHERE id = :indice";
$resultado = $pdo->prepare($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute([":indice" => $indice])) {
print " <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif ($resultado->fetchColumn() == 0) {
print " <p class=\"aviso\">Registro no encontrado.</p>\n";
} else {
...
Esta comprobación es la misma que la de listar.php
Esta comprobación es similar a la de comprobar que hay registros en la tabla, pero utilizando sentencias preparadas:
$consulta = "SELECT * FROM $cfg[tablaPersonas]
WHERE nombre LIKE :nombre
AND apellidos LIKE :apellidos";
$resultado = $pdo->prepare($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute([":nombre" => "%$nombre%", ":apellidos" => "%$apellidos%"])) {
print " <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (count($registros = $resultado->fetchAll()) == 0) {
print " <p class=\"aviso\">No se han encontrado registros.</p>\n";
} else {
...
Esta comprobación es la misma que la de listar.php
Esta comprobación es similar a la de comprobar que se ha marcado algún registro para borrar, pero en este caso no se trata de una matriz
$id = recoge("id");
if ($id == "") {
print " <p class=\"aviso\">No se ha seleccionado ningún registro.</p>\n";
} else {
...
Esta comprobación es similar a la de comprobar que se han encontrado registros de buscar-2.php.
$consulta = "SELECT * FROM $cfg[tablaPersonas]
WHERE id = :id";
$resultado = $pdo->prepare($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute([":id" => $id])) {
print " <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!($registro = $resultado->fetch())) {
print " <p class=\"aviso\">Registro no encontrado.</p>\n";
} else {
...
Esta comprobación es la misma que la de insertar-2.php
Esta comprobación es la misma que la de modificar-2.php, pero como en esta página se recogen varios datos, se puede recoger y comprobar el id como un dato más.
$idOk = false;
...
if ($id == "") {
print " <p class=\"aviso\">No se ha seleccionado ningún registro.</p>\n";
} else {
$idOk = true;
}
if ($nombreOk && $apellidosOk && $idOk) {
...
Esta comprobación es la misma que la de borrar-2.php
$consulta = "SELECT COUNT(*) FROM $cfg[tablaPersonas]
WHERE id = :id";
$resultado = $pdo->prepare($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute([":id" => $id])) {
print " <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif ($resultado->fetchColumn() == 0) {
print " <p class=\"aviso\">Registro no encontrado.</p>\n";
} else {
...
Esta comprobación es similar a la de insertar-2.php, excepto que es necesario incluir el id en la consulta para el caso particular en que la modificación realizada haya sido cambiar alguna minúscula por minúscula o viceversa. El motivo es que sólo se incluyera el nombre y los apellidos MySQL diría que sí que hay un registro como el modificado (el registro a modificar).
// La consulta cuenta los registros con un id diferente porque MySQL no distingue
// mayúsculas de minúsculas y si en un registro sólo se cambian mayúsculas por
// minúsculas MySQL diría que ya hay un registro como el que se quiere guardar.
$consulta = "SELECT COUNT(*) FROM $cfg[tablaPersonas]
WHERE nombre = :nombre
AND apellidos = :apellidos
AND id<>:id";
$resultado = $pdo->prepare($consulta);
if (!$resultado) {
print " <p class=\"aviso\">Error al preparar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif (!$resultado->execute([":nombre" => $nombre, ":apellidos" => $apellidos, ":id" => $id])) {
print " <p class=\"aviso\">Error al ejecutar la consulta. SQLSTATE[{$pdo->errorCode()}]: {$pdo->errorInfo()[2]}</p>\n";
} elseif ($resultado->fetchColumn() > 0) {
print " <p class=\"aviso\">Ya existe un registro con esos mismos valores. "
. "No se ha guardado la modificación.</p>\n";
} else {
...
En cualquier listado en forma de tabla, queremos poder ordenar los registros en un orden determinado (por cualquier columna, en orden ascendente o descendente).
Para ello deberíamos incluir un ORDER BY en las consultas que seleccionan los registros. Por ejemplo:
$consulta = "SELECT * FROM $cfg[tablaPersonas]
ORDER BY $ordena";
La columna y el orden deberían haberse recogido previamente. Al tratarse de un dato proporcionado por el usuario, por seguridad deberían utilizarse consultas preparadas, pero los nombres de columnas no se pueden proporcionar como parámetros. Para resolver este problema, utilizaremos una función de recogida especial que comprobará si el dato recibido es uno de los valores posibles (los valores posibles son los nombres de las columnas y el tipo de ordenación, ASC o DESC). En caso de no recibir un valor de ordenación o recibir un valor incorrecto, se asignará un valor predeterminado.
La función recogeValores() tendrá tres argumentos: el campo a recoger, una matriz con los valores posibles y el valor predeterminado de ordenación. En el ejemplo siguiente, el valor predeterminado es listar por orden alfabético del nombre:
$ordena = recogeValores("ordena", $cfg["tablaPersonasColumnasOrden"], "nombre ASC");
En biblioteca.php la función recogeValores() y la matriz de valores posibles podrían ser las siguientes:
// Valores de ordenación de la tabla
$cfg["tablaPersonasColumnasOrden"] = [
"nombre ASC", "nombre DESC",
"apellidos ASC", "apellidos DESC",
];
function recogeValores($var, $registrosValidos, $registroPredeterminado)
{
foreach ($registrosValidos as $registroValido) {
if (isset($_REQUEST[$var]) && $_REQUEST[$var] == $registroValido) {
return $registroValido;
}
}
return $registroPredeterminado;
}
Para ofrecer al usuario la posibilidad de ordenar los registros, podemos insertar en cada una de las cabeceras de cada columna de la tabla dos imágenes de flechas: y
, a ambos lados de los nombres de las columnas.
Para que al hacer clic en las imágenes, se actualice el contenido, cada imagen estará contenida en un botón que envíe el criterio de ordenación deseado.
print " <th>\n";
print " <button name=\"ordena\" value=\"nombre ASC\" class=\"boton-invisible\">\n";
print " <img src=\"abajo.svg\" alt=\"A-Z\" title=\"A-Z\" width=\"15\" height=\"12\">\n";
print " </button>\n";
print " Nombre\n";
print " <button name=\"ordena\" value=\"nombre DESC\" class=\"boton-invisible\">\n";
print " <img src=\"arriba.svg\" alt=\"Z-A\" title=\"Z-A\" width=\"15\" height=\"12\">\n";
print " </button>\n";
print " </th>\n";
Por tanto, estas páginas deben incluir un formulario.
Para conseguirlo, el atributo action del formulario señalará a la propia página. Para referirse a la propia página se puede escribir su nombre o utilizar la variable predefinida $_SERVER[PHP_SELF]:
print " <form action=\"$_SERVER[PHP_SELF]\" method=\"$cfg[formMethod]\">\n";
Además, en el caso de la búsqueda (buscar-2.php), el enlace a la propia página debe enviar también los valores que introdujo el usuario en la búsqueda, por lo que los añadiremos en controles ocultos:
print " <p>\n";
print " <input type=\"hidden\" name=\"nombre\" value=\"$nombre\">\n";
print " <input type=\"hidden\" name=\"apellidos\" value=\"$apellidos\">\n";
print " </p>\n";
Para conseguirlo, el botón Enviar de las páginas borrar-1.php y modificar-1.php incluirá el atributo formaction, que permite enviar la información a una página distinta de la indicada por el atributo action del formulario.
print " <p>\n";
print " <input type=\"submit\" value=\"Borrar registro\" formaction=\"borrar-2.php\">\n";
print " <input type=\"reset\" value=\"Reiniciar formulario\">\n";
print " </p>\n";
Las páginas borrar-1.php y modificar-1.php deberían recoger también las casillas de verificación y los botones radio, para que si se marcan algunas casillas o botones y se reordenan los registros, se puedan mantener marcadas las casillas ya marcadas.
$id = recoge("id", []);
...
foreach ($resultado as $registro) {
print " <tr>\n";
if (isset($id[$registro["id"]])) {
print " <td class=\"centrado\"><input type=\"checkbox\" name=\"id[$registro[id]]\" checked></td>\n";
} else {
print " <td class=\"centrado\"><input type=\"checkbox\" name=\"id[$registro[id]]\"></td>\n";
}
print " <td>$registro[nombre]</td>\n";
print " <td>$registro[apellidos]</td>\n";
print " </tr>\n";
}