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
$comprobacionOk = false; // Inicialmente, la comprobación se considera no superada (valor false)
...
if ( ... ) { // Si no se cumple aquello que se quiere comprobar ...
print " <p class=\"aviso\">Error ... // ... se muestra un aviso
} else {
$comprobacionOk = true; // Si se cumple, la variable $...Ok toma el valor true
}
if ($comprobacionOk) { // Para que el programa continúe, la comprobación tiene que haber tenido éxito
...
$comprobacion1Ok = false; // Realizamos una primera comprobación
...
$comprobacion2Ok = false; // Realizamos una segunda comprobación ...
if ($comprobacion1Ok) { // ... si se ha superado la primera.
...
}
$comprobacion3Ok = false; // Realizamos una tercera comprobación ...
if ($comprobacion1Ok && $comprobacion2Ok) { // ... si se han superado las dos primeras.
...
}
...
// 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().
La variable lógica $limiteRegistrosOk tendrá el valor true si no se ha alcanzado el límite de número de registros y false si se ha alcanzado.
// Comprobamos si se ha alcanzado el número máximo de registros en la tabla
$limiteRegistrosOk = false;
$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 {
$limiteRegistrosOk = true;
}
if ($limiteRegistrosOk) {
print " <form action=\"insertar-2.php\" method=\"$cfg[formMethod]\">\n";
...
La variable lógica $registroNoVacioOk tendrá el valor true si algún campo no es vacío y false si todos los campos son vacíos.
// Comprobamos que no se intenta crear un registro vacío
$registroNoVacioOk = false;
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";
} else {
$registroNoVacioOk = true;
}
Esta consulta se debe realizar con sentencias preparadas ya que incluye datos proporcionados por el usuario.
La variable lógica $registroDistintoOk tendrá el valor true si el registro no se encuentra en la tabla y false si se encuentra.
// Comprobamos que no se intenta crear un registro idéntico a uno que ya existe
$registroDistintoOk = false;
if ($nombreOk && $apellidosOk && $registroNoVacioOk) {
$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 {
$registroDistintoOk = true;
}
}
Esta comprobación es idéntica a la realizada en insertar-1.php, pero es conveniente repetirla aquí, ya que otro usuario puede haber introducido registros mientras el primer usuario estaba rellenando el formulario de insertar-1.php.
// Comprobamos si se ha alcanzado el número máximo de registros en la tabla
$limiteRegistrosOk = false;
if ($nombreOk && $apellidosOk && $registroNoVacioOk && $registroDistintoOk) {
$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 {
$limiteRegistrosOk = true;
}
}
if ($nombreOk && $apellidosOk && $registroNoVacioOk && $registroDistintoOk && $limiteRegistrosOk) {
// Insertamos el registro en la tabla
$consulta = "INSERT INTO $cfg[tablaPersonas]
(nombre, apellidos)
VALUES (:nombre, :apellidos)";
...
La variable lógica $hayRegistrosOk tendrá el valor true si la tabla contiene registros y false si está vacía.
$hayRegistrosOk = false;
$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() == 0) {
print " <p class=\"aviso\">No se ha creado todavía ningún registro.</p>\n";
} else {
$hayRegistrosOk = true;
}
if ($hayRegistrosOk) {
$consulta = "SELECT * 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";
} else {
print " <p>Listado completo de registros:</p>\n";
...
Esta comprobación es la misma que la de listar.php
En el bucle foreach que recorre la matriz $id recibida comprobaremos que los valores de id corresponden a registros existentes en la tabla.
La variable lógica $registroEncontradoOk tendrá el valor true si existe el registro con el id recibido y false si el registro no existe.
En caso de superar la comprobación, borraremos el registro.
if ($idOk) {
foreach ($id as $indice => $valor) {
// Comprobamos que el registro con el id recibido existe en la base de datos
$registroEncontradoOk = false;
$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 {
$registroEncontradoOk = true;
}
// Si todas las comprobaciones han tenido éxito ...
if ($registroEncontradoOk) {
// Borramos el registro con el id recibido
$consulta = "DELETE FROM $cfg[tablaPersonas]
WHERE id = :indice";
...
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 puesto que se incluye las cadenas de búsqueda indicadas por el usuario en buscar-1:
$nombre = recoge("nombre");
$apellidos = recoge("apellidos");
$registrosEncontradosOk = false;
$consulta = "SELECT COUNT(*) 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 ($resultado->fetchColumn() == 0) {
print " <p class=\"aviso\">No se han encontrado registros.</p>\n";
} else {
$registrosEncontradosOk = true;
}
if ($registrosEncontradosOk) {
// Seleccionamos todos los registros con las condiciones de búsqueda recibidas
$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";
} else {
print " <p>Registros encontrados:</p>\n";
...
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, sino de un valor escalar.
La variable lógica $idOk tendrá el valor true si la variable $id no es vacía y false si es vacía.
$id = recoge("id");
// Comprobamos el dato recibido
$idOk = false;
if ($id == "") {
print " <p class=\"aviso\">No se ha seleccionado ningún registro.</p>\n";
} else {
$idOk = true;
}
Esta comprobación es similar a la de comprobar que se han encontrado registros de buscar-2.php.
$registroEncontradoOk = false;
if ($idOk) {
$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 {
$registroEncontradoOk = true;
}
}
if ($idOk && $registroEncontradoOk) {
$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";
} else {
$registro = $resultado->fetch();
print " <form action=\"modificar-3.php\" method=\"$cfg[formMethod]\">\n";
print " <p>Modifique los campos que desee:</p>\n";
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;
}
Esta comprobación es la misma que la de insertar-2.php
// Comprobamos que no se intenta crear un registro vacío
$registroNoVacioOk = false;
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";
} else {
$registroNoVacioOk = true;
}
Esta comprobación es la misma que la de borrar-2.php
$registroEncontradoOk = false;
if ($nombreOk && $apellidosOk && $idOk && $registroNoVacioOk) {
$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 {
$registroEncontradoOk = true;
}
}
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 variable lógica $registroDistintoOk tendrá el valor true si no existe ningún registro con los nuevos valores y false si ya existe un registro con los nuevos valores.
// Comprobamos que no se intenta crear un registro idéntico a uno que ya existe
$registroDistintoOk = false;
if ($nombreOk && $apellidosOk && $idOk && $registroNoVacioOk && $registroEncontradoOk) {
// 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 {
$registroDistintoOk = true;
}
}
if ($nombreOk && $apellidosOk && $idOk && $registroNoVacioOk && $registroEncontradoOk && $registroDistintoOk) {
// Actualizamos el registro con los datos recibidos
$consulta = "UPDATE $cfg[tablaPersonas]
SET nombre = :nombre, apellidos = :apellidos
WHERE id = :id";
...
Estos son los fragmentos de código que se deben añadir a las páginas del ejercicio Bases de datos (2) 1 para resolver el ejercicio Bases de datos (2) 2.
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 recoge() tendrá tres argumentos: el campo a recoger, una matriz con los valores posibles (argumento allowed) y el valor predeterminado de ordenación (argumento default). En el ejemplo siguiente, el valor predeterminado es listar por orden alfabético del nombre:
$ordena = recoge("ordena", default: "nombre ASC", allowed: $cfg["tablaPersonasColumnasOrden"]);
En biblioteca.php la matriz de valores posibles de ordenación podría ser la siguiente:
// Valores de ordenación de la tabla
$cfg["tablaPersonasColumnasOrden"] = [
"nombre ASC", "nombre DESC",
"apellidos ASC", "apellidos DESC",
];
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";
}
$id = recoge("id");
...
foreach ($resultado as $registro) {
print " <tr>\n";
if ($id == $registro["id"]) {
print " <td class=\"centrado\"><input type=\"radio\" name=\"id\" value=\"$registro[id]\" checked></td>\n";
} else {
print " <td class=\"centrado\"><input type=\"radio\" name=\"id\" value=\"$registro[id]\"></td>\n";
}
print " <td>$registro[nombre]</td>\n";
print " <td>$registro[apellidos]</td>\n";
print " </tr>\n";
}