Cómo crear una aplicación CRUD con PHP y MySQL

MySQLPHP

En este tutorial vamos a ver cómo crear una aplicación CRUD con PHP y MySQL. Crearemos tanto el backend como el frontend de la aplicación. No usaremos ningún framework, sino las extensiones y los métodos nativos de PHP. La aplicación soportará la lectura, la escritura, la actualización y el borrado de registros en la base de datos. Para las vistas usaremos únicamente HTML y CSS.

Este es un tutorial de introducción especialmente creado para aquellas personas que se estén iniciando con el desarrollo de aplicaciones con PHP. Por ello, no crearemos un gestor de rutas ni usaremos composer. Tampoco dividiremos la aplicación en modelos, vistas y controladores tal y como dicta el patrón de diseño MVC, siendo algo que dejaremos para otro tutorial.

Introducción

Vamos a crear una sencilla aplicación que permita gestionar los datos de los alumnos de un colegio. Necesitamos agregar las funcionalidades que nos permitan crear un alumno, mostrar una lista de alumnos, editar los datos de un alumno y eliminar un alumno.

Intentaremos usar las funcionalidades más recientes posibles de PHP y tomaremos ciertas consideraciones de seguridad, aun que este tutorial no deja de ser de aprendizaje. De todos modos, antes de continuar, es recomendable que tengas unos conocimientos básicos de HTML y de PHP:

  • Si nunca has usado HTML, consulta primero la guía definitiva de HTML para aprender los conceptos más importantes.
  • Si nunca has usado PHP, consulta la guía definitiva de PHP, en donde además de aprender tus primeros pasos, verás cómo instalar y configurar PHP en tu sistema operativo.
  • Para gestionar las bases de datos MySQL usaremos phpMyAdmin, que es una herramienta que se instala con paquetes como Wamp, XAMPP o MAMP, aunque también puedes usar herramientas como Sequel Pro. De todos modos, también veremos cómo crear todas las consultas MySQL manualmente.

Lo primero que veremos en este tutorial será cómo conectarnos a una base de datos MySQL con PHP usando PDO (PHP Data Objects). Seguidamente crearemos un script que cree tanto la base de datos como las tablas de la misma. Luego crearemos un formulario HTML que envíe datos al servidor. En el servidor, usaremos sentencias preparadas para insertar registros en la base da datos. Finalmente obtendremos los datos de la base de datos y los mostraremos en una tabla HTML.

Puedes encontrar el código de la aplicación CRUD en GitHub.

Configuración del Host

Lo primero que tenemos que hacer es crear un host virtual en nuestro sistema, que es en donde crearemos el proyecto. Usaremos el servidor Apache, que se instala con Wamp, que es la herramienta que utilizo en este proyecto. Si no sabes cómo crear un host virtual, consulta el tutorial en donde explico cómo crear un host virtual con Apache. En caso de que uses algún paquete todo en uno, consulta una de estas guías.

Vamos a crear el host virtual tutorial-crud.localhost. En mi caso, he asignado el directorio /hosts/tutorial-crud al host virtual, pero puedes asignar el que prefieras.

Creación de la plantilla HTML

Primero tenemos que crear una plantilla HTML para la aplicación junto con un pequeño menú. No es el objetivo de este tutorial el de aprender CSS, por lo que nos limitaremos a usar Bootstrap para los estilos.

En el interior del directorio del host virtual, crea un archivo vacío llamado index.php. En teoría deberíamos agregar el código HTML de la aplicación en el archivo index.php, tal que así:

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="x-ua-compatible" content="ie=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <title>Aplicación CRUD PHP</title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" />
  </head>

  <body>
    <h1>Aplicación CRUD PHP</h1>
    <!-- Aquí el código HTML de la aplicación -->
  </body>
</html>

Sin embargo, vamos a dividir la plantilla HTML en una cabecera y un pie que compartirán todas las páginas de nuestra aplicación, por lo que, por ahora, es mejor que dejes el archivo index.php vacío.

Para ello, crea un directorio llamado /templates, en cuyo interior debes crear un archivo llamado header.php y otro llamado footer.php.

En archivo header.php contendrá la cabecera de la aplicación, por lo que debes agregar este código HTML:

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="x-ua-compatible" content="ie=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <title>Aplicación CRUD PHP</title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" />
  </head>
  <body>

Lo único que hemos hecho es agregar la declaración DOCTYPE y la sección head de la aplicación, en donde hemos agregado el título y también hemos incluido Bootstrap desde su CDN.

Seguidamente, debemos agregar el código del archivo footer.php, que se limitará a cerrar las etiquetas body y html con este código:

  </body>
</html>

Luego edita el archivo index.php y modifícalo para que contenga únicamente este código, en donde agregamos el header y el footer que hemos creado:

<?php include "templates/header.php"; ?>
  <!-- Aquí el código HTML de la aplicación -->
<?php include "templates/footer.php"; ?>

A continuación vamos configurar la base de datos de la aplicación.

Configuración de la base de datos

Para crear la base datos puedes usar alguna aplicación como MySQL Workbench, SequelPro, Adminer o phpMyAdmin. En este tutorial, primero crearemos la base de datos mediante una consulta SQL.

Crea la base de datos

Crea un directorio en la aplicación llamado /data y, en su interior, crea un archivo vacío llamado, migracion.sql. En su interior, agrega esta consulta:

CREATE DATABASE tutorial_crud;

use tutorial_crud;

CREATE TABLE alumnos (
  id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  nombre VARCHAR(30) NOT NULL,
  apellido VARCHAR(30) NOT NULL,
  email VARCHAR(50) NOT NULL,
  edad INT(3),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Lo que hemos hecho en este script es crear la base de datos, a la que llamamos tutorial_crud. Luego usamos la sentencia use para seleccionar la base de datos que hemos creado y luego creamos la tabla alumnos junto a sus campos. Primero definimos el nombre de los campos y seguidamente su tipo:

  • Usamos int(11) para definir campos que contengan un número entero con una longitud de hasta 11 números.
  • Usamos varchar(n) para definir cadenas que contengan hasta n caracteres.
  • Usamos TIMESTAMP para definir un campo que contenga una fecha en formato YYYY-MM-DD HH:MI:SS.

Los campos created_at y updated_at guardarán la fecha de creación y de actualización de los alumnos respectivamente. Además, también hemos usado la sentencia NOT NULL para evitar que los campos puedan estar vacíos. No será posible insertar un registro en la tabla si un campo NOT NULL está vacío.

También asignamos la fecha actual como la fecha por defecto de los campos created_at y updated_at mediante la sentencia DEFAULT y el valor CURRENT_TIMESTAMP.

Si quieres puedes probar la consulta antes de usarla en el código. Para ello sigue estos los pasos que ves a continuación.

  • Accede a phpMyAdmin o a la herramienta que utilices:
  • Luego accede a la sección que permite ejecutar consulta SQL y copia y pega la consulta anterior:
  • Luego ejecuta la consulta haciendo clic en continuar en el caso de phpMyAdmin. Esta consulta debería crear la base de datos.

Si has creado la base de datos y no ha habido errores, significa que todo funciona como debería. Seguidamente borra la base de datos, ya que lo que pretendemos es crearla mediante un script PHP.

Crea el script de instalación

Vamos a crear un script que nos permita conectarnos a MySQL para crear la base de datos. Podemos usar la interfaz PDO (PHP Data Objects) o podemos usar MySQLi. La diferencia consiste en que con PDO nos podemos conectar a más bases de datos que no necesariamente han de ser MySQL, siendo más versátil que MySQLi, que solamente funcionará con bases de datos MySQL. Además, PDO es más extensible y abierto de cara al futuro, haciendo que las aplicaciones sean más fáciles de mantener.

El constructor de la clase PDO necesita que le pasemos el host de conexión a la base de datos, el nombre de usuario MySQL, la contraseña y finalmente las opciones de conexión. Para evitar repetir estos datos en varios archivos, crea el archivo de configuración config.php en el directorio raíz de la aplicación:

<?php

return [
  'db' => [
    'host' => 'localhost',
    'user' => 'root',
    'pass' => 'root',
    'name' => 'tutorial_crud',
    'options' => [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]
  ]
];

Lo que hemos hecho ha sido definir un array de configuración que será vuelto por el archivo. En el array db hemos definido los parámetros y opciones que usaremos para conectarnos a la base de datos.

Ahora crea un archivo llamado instalar.php en el directorio raíz del proyecto y, en su interior, incluye el array de configuración y asigna una nueva instancia de la clase PDO a una variable, a la que llamaremos $conexion.  Para crear el objeto PDO usaremos los datos del array de configuración:

$config = include 'config.php';
$conexion = new PDO('mysql:host=' . $config['db']['host'], $config['db']['user'], $config['db']['pass'], $config['db']['options']);

Como ves, el primer parámetro es una cadena de texto que incluye el tipo de la base de datos, el host y opcionalmente el nombre de la base de datos como parte de ella. A este cadena se le llama DSN.

Ahora que ya hemos creado la conexión, vamos a asignar nuestra consulta SQL a una variable usando el método file_get_contents y, seguidamente, usaremos el método exec para ejecutar la consulta:

$sql = file_get_contents('data/migracion.sql');
$conexion->exec($sql);

A continuación puedes ver el contenido final del archivo instalar.php, en el que también hemos agregado un bloque try/catch para que se muestre algún error en caso de haberlo:

<?php
$config = include 'config.php';

try {
  $conexion = new PDO('mysql:host=' . $config['db']['host'], $config['db']['user'], $config['db']['pass'], $config['db']['options']);
  $sql = file_get_contents("data/migracion.sql");
  
  $conexion->exec($sql);
  echo "La base de datos y la tabla de alumnos se han creado con éxito.";
} catch(PDOException $error) {
  echo $error->getMessage();
}

Tal y como ves, la sentencia catch del bloque try/catch recibirá una excepción de tipo PDOException como parámetro.

Ejecuta el script de instalación

Para ejecutar el script de instalación, accede a la URL /instalar.php desde tu navegador. En mi caso, accederé a la URL tutorial-crud.localhost/instalar.php. Deberías ver el siguiente mensaje:

Si se muestra un mensaje de error en el que se dice que la base de datos ya existe, recuerda que deberías haberla eliminado desde la aplicación gestora de bases de datos.

Creación de la aplicación CRUD

Tenemos que crear una página que nos permita crear un alumno, otra que nos permita listarlos y otra que nos permita actualizarlos. Además, también necesitaremos un script que sea capaz de eliminar usuarios. Veamos cada uno de estos apartados de la aplicación CRUD por separado.

CRUD (Create): Creación de alumnos

Crea un archivo llamado crear.php en el directorio raíz de la aplicación. En este archivo agregaremos un formulario que nos permita crear un alumno. Pero primero debemos agregar un enlace al mismo en el archivo index.php:

<?php include "templates/header.php"; ?>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <a href="crear.php"  class="btn btn-primary mt-4">Crear alumno</a>
      <hr>
    </div>
  </div>
</div>

<?php include "templates/footer.php"; ?>

Lo único que hemos hecho ha sido agregar un botón que nos permita acceder al archivo create.php.

Formulario HTML

Primero incluiremos los archivos header.php y footer.php en el archivo crear.php. También agregaremos un formulario HTML que nos permita introducir los datos de un alumno:

<?php include "templates/header.php"; ?>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <h2 class="mt-4">Crea un alumno</h2>
      <hr>
      <form method="post">
        <div class="form-group">
          <label for="nombre">Nombre</label>
          <input type="text" name="nombre" id="nombre" class="form-control">
        </div>
        <div class="form-group">
          <label for="apellido">Apellido</label>
          <input type="text" name="apellido" id="apellido" class="form-control">
        </div>
        <div class="form-group">
          <label for="email">Email</label>
          <input type="email" name="email" id="email" class="form-control">
        </div>
        <div class="form-group">
          <label for="edad">Edad</label>
          <input type="text" name="edad" id="edad" class="form-control">
        </div>
        <div class="form-group">
          <input type="submit" name="submit" class="btn btn-primary" value="Enviar">
          <a class="btn btn-primary" href="index.php">Regresar al inicio</a>
        </div>
      </form>
    </div>
  </div>
</div>

<?php include "templates/footer.php"; ?>

Hemos agregado el atributo name a cada campo <input> del formulario. El valor del atributo name será el nombre que tendrá cada campo cuando se envíe el formulario.

Hemos agregado también una etiqueta <label> para cada campo, que se relacionará con su correspondiente campo gracias al atributo for. El valor del atributo for es el mismo que el del atributo id del campo con el que se relaciona. El uso de etiquetas label permite que las páginas sean más accesibles.

No hemos especificado ninguna acción en el formulario, por lo que éste se enviará a la misma página en la que está definido. Por ahora, dado que todavía no hemos agregado el código PHP, no ocurrirá nada.

Si accedes al proyecto desde tu navegador, este será el resultado del formulario:

Tras crear el formulario, pasaremos a la parte backend, que es el código que ejecutará el servidor para insertar el usuario en la base de datos.

Código PHP

Cuando envías el formulario, los datos se enviarán al propio script crear.php. Podrás encontrarlos indexados en el interior del array $_POST. Por ejemplo, el nombre que introduzcas en el campo cuyo atributo name es nombre, estará en la variable $_POST['nombre']. Del mismo modo, el nombre que introduzcas en el campo cuyo atributo name es apellido, estará en la variable $_POST['apellido'].

Para comprobar si el formulario se ha enviado, puedes usar la siguiente sentencia:

if (isset($_POST['submit'])) {
   // Acciones a realizar
}

Para insertar un usuario también debemos conectarnos a MySQL, pero ahora deberemos especificar también el nombre de la base de datos en el parámetros DSN.

A continuación, agrega este código al inicio del archivo crear.php:

<?php

if (isset($_POST['submit'])) {
 
  $resultado = [
    'error' => false,
    'mensaje' => 'Usuario agregado con éxito'
  ];
  
  $config = include 'config.php';

  try {
    $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
    $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);

    // Código que insertará un alumno

  } catch(PDOException $error) {
    $resultado['error'] = true;
    $resultado['mensaje'] = $error->getMessage();
  }
}

El array $resultado almacenará algún posible error, de haberlo. Luego hemos incluido el array de configuración del archivo config.php y nos hemos conectado a la base de datos. Esta vez hemos definido el nombre de la base de datos a la que nos conectamos.

Hemos usado un bloque try/catch, en cuyo interior insertamos el usuario. De haber algún error, se ejecutará el bloque catch, en donde almacenamos el error en el array resultado.

Tal y como ves, he escrito un comentario con la localización en la que debes agregar el código encargado de crear un nuevo usuario en la base de datos. Ahora debes crear un array con los datos del nuevo alumno, que obtendremos del array $_POST:

$alumno = [
  "nombre"   => $_POST['nombre'],
  "apellido" => $_POST['apellido'],
  "email"    => $_POST['email'],
  "edad"     => $_POST['edad'],
];

En  teoría deberíamos sanitizar los datos de entrada. Sin embargo, dado que usaremos sentencias preparadas de PHP, no es necesario. Usaremos una sentencia INSERT MySQL:

INSERT INTO users (nombre, apellido, email, edad) values (:nombre, :apellido, :email, :edad)

Este sería el código PHP que implementa la consulta. Hemos usado dos líneas para que sea más legible:

$consultaSQL = "INSERT INTO alumnos (nombre, apellido, email, edad)";
$consultaSQL .= "values (:" . implode(", :", array_keys($alumno)) . ")";

A continuación vamos a usar el método prepare y a ejecutar la consulta:

$sentencia = $conexion->prepare($consultaSQL);
$sentencia->execute($alumno);

Vamos a agregar también un mensaje de confirmación justo después del lugar en el que incluimos el archivo header.php, en donde mostraremos un error, de haberlo, o un mensaje de éxito si el alumno se ha insertado correctamente:

<?php
if (isset($resultado)) {
  ?>
  <div class="container mt-3">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-<?= $resultado['error'] ? 'danger' : 'success' ?>" role="alert">
          <?= $resultado['mensaje'] ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

Este sería el código PHP completo que usamos para crear el usuario:

<?php
if (isset($_POST['submit'])) {
  $resultado = [
    'error' => false,
    'mensaje' => 'El alumno ' . $_POST['nombre'] . ' ha sido agregado con éxito' 
  ];
  $config = include 'config.php';

  try {
    $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
    $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);

    $alumno = array(
      "nombre"   => $_POST['nombre'],
      "apellido" => $_POST['apellido'],
      "email"    => $_POST['email'],
      "edad"     => $_POST['edad'],
    );
    
    $consultaSQL = "INSERT INTO alumnos (nombre, apellido, email, edad)";
    $consultaSQL .= "values (:" . implode(", :", array_keys($alumno)) . ")";
    
    $sentencia = $conexion->prepare($consultaSQL);
    $sentencia->execute($alumno);

  } catch(PDOException $error) {
    $resultado['error'] = true;
    $resultado['mensaje'] = $error->getMessage();
  }
}
?>

<?php include "templates/header.php"; ?>

<?php
if (isset($resultado)) {
  ?>
  <div class="container mt-3">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-<?= $resultado['error'] ? 'danger' : 'success' ?>" role="alert">
          <?= $resultado['mensaje'] ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

Ahora ya podrías enviar el formulario. Deberías ver el siguiente mensaje una vez lo envíes:

Si embargo, todavía podemos mejorar el formulario.

Ataques XSS

Cuando creamos un alumno con éxito, mostramos su nombre en el mensaje de confirmación. Imprimimos por pantalla directamente el resultado de la variable $_POST, lo cual podría tener ciertos efectos indeseables si algún atacante inyecta código malicioso en esta variable.

Para evitar ataques XSS vamos a codificar los caracteres especiales en sus respectivas versiones HTML. Para simplificar el proceso, crearemos una función reutilizable.

Crea el archivo funciones.php en el directorio raíz del proyecto y agrega esta función:

function escapar($html) {
  return htmlspecialchars($html, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
}

Lo que hace la función es codificar cualquier caracter en su versión HTML. Luego, incluye el archivo funciones.php en la parte superior del archivo create.php:

include 'funciones.php';

Seguidamente, usa la función escapar con el elemento $_POST['nombre'] en el array $resultado:

$resultado = [
  'error' => false,
  'mensaje' => 'El alumno ' . escapar($_POST['nombre']) . ' ha sido agregado con éxito'
];

Este sería el código final completo del archivo crear.php:

<?php

include 'funciones.php';

if (isset($_POST['submit'])) {
  $resultado = [
    'error' => false,
    'mensaje' => 'El alumno ' . escapar($_POST['nombre']) . ' ha sido agregado con éxito'
  ];

  $config = include 'config.php';

  try {
    $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
    $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);

    $alumno = array(
      "nombre"   => $_POST['nombre'],
      "apellido" => $_POST['apellido'],
      "email"    => $_POST['email'],
      "edad"     => $_POST['edad'],
    );

    $consultaSQL = "INSERT INTO alumnos (nombre, apellido, email, edad) values (:" . implode(", :", array_keys($alumno)) . ")";

    $sentencia = $conexion->prepare($consultaSQL);
    $sentencia->execute($alumno);

  } catch(PDOException $error) {
    $resultado['error'] = true;
    $resultado['mensaje'] = $error->getMessage();
  }
}
?>

<?php include 'templates/header.php'; ?>

<?php
if (isset($resultado)) {
  ?>
  <div class="container mt-3">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-<?= $resultado['error'] ? 'danger' : 'success' ?>" role="alert">
          <?= $resultado['mensaje'] ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <h2 class="mt-4">Crea un alumno</h2>
      <hr>
      <form method="post">
        <div class="form-group">
          <label for="nombre">Nombre</label>
          <input type="text" name="nombre" id="nombre" class="form-control">
        </div>
        <div class="form-group">
          <label for="apellido">Apellido</label>
          <input type="text" name="apellido" id="apellido" class="form-control">
        </div>
        <div class="form-group">
          <label for="email">Email</label>
          <input type="email" name="email" id="email" class="form-control">
        </div>
        <div class="form-group">
          <label for="edad">Edad</label>
          <input type="text" name="edad" id="edad" class="form-control">
        </div>
        <div class="form-group">
          <input type="submit" name="submit" class="btn btn-primary" value="Enviar">
          <a class="btn btn-primary" href="index.php">Regresar al inicio</a>
        </div>
      </form>
    </div>
  </div>
</div>

<?php include 'templates/footer.php'; ?>

CRUD (Read): Lista de alumnos

Vamos a crear la página usada para mostrar los datos de los alumnos. Para ello usaremos el archivo index.php, en donde incluiremos tanto el archivo  header.php como el archivo footer.php.

Código PHP

Después del botón que nos permite acceder a la página crear.php vamos a agregar una tabla con la lista de alumnos existentes. Sin embargo, primero necesitamos obtener la lista de alumnos desde la base de datos. Por ello, debes agregar este código al principio del archivo index.php:

<?php
include 'funciones.php';

$error = false;
$config = include 'config.php';

try {
  $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
  $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);

  // Código que obtendrá la lista de alumnos

} catch(PDOException $error) {
  $error = $error->getMessage();
}
?>

Lo que hemos hecho ha sido incluir el archivo funciones.php y también el array de configuración. Luego nos conectamos a la base de datos en un bloque try/catch. De haber algún error, lo almacenamos en la variable $error.

La consulta MySQL que usaremos para obtener la lista de alumnos será la siguiente, que sencillamente obtendrá la lista completa de alumnos

$sql = "SELECT * FROM alumnos;

Este será el código PHP que implementa y ejecuta la consulta:

$consultaSQL = "SELECT * FROM alumnos";

$sentencia = $conexion->prepare($consultaSQL);
$sentencia->execute();

Luego almacenamos el resultado en la variable $alumnos:

$alumnos = $sentencia->fetchAll();

En caso de que se produzca algún error debes mostrarlo. Para ello, agrega el siguiente código justo después del lugar en donde incluimos el archivo header.php:

<?php
if ($error) {
  ?>
  <div class="container mt-2">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-danger" role="alert">
          <?= $error ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

Este sería el código PHP del archivo index.php con lo que hemos hecho hasta ahora.

<?php
include 'funciones.php';

$error = false;
$config = include 'config.php';

try {
  $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
  $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);

  $consultaSQL = "SELECT * FROM alumnos";

  $sentencia = $conexion->prepare($consultaSQL);
  $sentencia->execute();

  $alumnos = $sentencia->fetchAll();

} catch(PDOException $error) {
  $error= $error->getMessage();
}
?>

<?php include "templates/header.php"; ?>

<?php
if ($error) {
  ?>
  <div class="container mt-2">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-danger" role="alert">
          <?= $error ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <a href="crear.php"  class="btn btn-primary mt-4">Crear alumno</a>
      <hr>
    </div>
  </div>
</div>

<?php include "templates/footer.php"; ?>

Ahora ya  solamente nos falta mostrar los resultados en una tabla.

Tabla HTML

Vamos a mostrar la lista de alumnos en una tabla HTML. Sin embargo, primero agregaremos un mensaje que se ejecute en caso de que se haya producido algún error al obtener la lista de alumnos.

A continuación puedes ver el código de la tabla. Agrega este código justo después del bloque en donde mostramos el botón que redirige a la página que permite crear alumnos, y antes del lugar en donde incluimos el archivo footer.php:

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <h2 class="mt-3">Lista de alumnos</h2>
      <table class="table">
        <thead>
          <tr>
            <th>#</th>
            <th>Nombre</th>
            <th>Apellido</th>
            <th>Email</th>
            <th>Edad</th>
          </tr>
        </thead>
        <tbody>
          <?php
          if ($alumnos && $sentencia->rowCount() > 0) {
            foreach ($alumnos as $fila) {
              ?>
              <tr>
                <td><?php echo escapar($fila["id"]); ?></td>
                <td><?php echo escapar($fila["nombre"]); ?></td>
                <td><?php echo escapar($fila["apellido"]); ?></td>
                <td><?php echo escapar($fila["email"]); ?></td>
                <td><?php echo escapar($fila["edad"]); ?></td>
              </tr>
              <?php
            }
          }
          ?>
        <tbody>
      </table>
    </div>
  </div>
</div>

Lo que hacemos es comprobar que existen alumnos mediante el método rowCount y recorrer la lista de alumnos, agregando una fila a la tabla por cada uno de los alumnos.

Este sería el código del archivo index.php con lo que hemos hecho hasta ahora:

<?php
include 'funciones.php';

$error = false;
$config = include 'config.php';

try {
  $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
  $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);

  $consultaSQL = "SELECT * FROM alumnos";

  $sentencia = $conexion->prepare($consultaSQL);
  $sentencia->execute();

  $alumnos = $sentencia->fetchAll();

} catch(PDOException $error) {
  $error= $error->getMessage();
}
?>

<?php include "templates/header.php"; ?>

<?php
if ($error) {
  ?>
  <div class="container mt-2">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-danger" role="alert">
          <?= $error ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <a href="crear.php"  class="btn btn-primary mt-4">Crear alumno</a>
      <hr>
    </div>
  </div>
</div>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <h2 class="mt-3">Lista de alumnos</h2>
      <table class="table">
        <thead>
          <tr>
            <th>#</th>
            <th>Nombre</th>
            <th>Apellido</th>
            <th>Email</th>
            <th>Edad</th>
          </tr>
        </thead>
        <tbody>
          <?php
          if ($alumnos && $sentencia->rowCount() > 0) {
            foreach ($alumnos as $fila) {
              ?>
              <tr>
                <td><?php echo escapar($fila["id"]); ?></td>
                <td><?php echo escapar($fila["nombre"]); ?></td>
                <td><?php echo escapar($fila["apellido"]); ?></td>
                <td><?php echo escapar($fila["email"]); ?></td>
                <td><?php echo escapar($fila["edad"]); ?></td>
              </tr>
              <?php
            }
          }
          ?>
        <tbody>
      </table>
    </div>
  </div>
</div>

<?php include "templates/footer.php"; ?>

Si agregas algunos alumnos y luego accedes a la página principal de la aplicación usando tu navegador, deberías ver este resultado:

Ya hemos terminado, pero vamos a agregar una funcionalidad a mayores. Se trata de un formulario que nos permitirá filtrar la lista de alumnos.

Búsqueda

Estaría bien agregar un campo de búsqueda que nos permita buscar usuarios por apellido. Para ello crearemos el siguiente formulario debajo de la línea de separación que hemos agregado, debajo del enlace hacia la página crear.php:

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <a href="crear.php"  class="btn btn-primary mt-4">Crear alumno</a>
      <hr>
      <form method="post" class="form-inline">
        <div class="form-group mr-3">
          <input type="text" id="apellido" name="apellido" placeholder="Buscar por apellido" class="form-control">
        </div>
        <button type="submit" name="submit" class="btn btn-primary">Ver resultados</button>
      </form>
    </div>
  </div>
</div>

Hemos usado el atributo placeholder en el campo nombre del formulario para mostrar un texto por defecto, que desaparecerá cuando introduzcamos algo en él.

Seguidamente, accede al proyecto desde tu navegador para ver el resultado:

El formulario enviará el apellido del alumno que introduzcamos a la propia página, por lo que debemos agregar una consulta SQL alternativa que se ejecute cuando el formulario se haya enviado:

if (isset($_POST['apellido'])) {
  $consultaSQL = "SELECT * FROM alumnos WHERE apellido LIKE '%" . $_POST['apellido'] . "%'";
} else {
  $consultaSQL = "SELECT * FROM alumnos";
}

El operador MySQL LIKE buscará los alumnos cuyo apellido contenga la subadena que introduzcamos en el campo apellido. El símbolo % sirve para especificar que puede haber texto a la izquierda o a la derecha de la cadena.

Vamos a modificar también el título del formulario, de modo que indiquemos cuando estemos realizando una búsqueda por apellido. Para ello, asignaremos el título del formulario mediante PHP:

$titulo = isset($_POST['apellido']) ? 'Lista de alumnos (' . $_POST['apellido'] . ')' : 'Lista de alumnos';

Hemos usado el operador ternario de PHP para comprobar si el apellido está presente en el array $_POST, en cuyo caso agregamos el apellido entre paréntesis al título:

Por ahora hemos visto cómo conectarnos a la base de datos mediante PDO, cómo crear un script de instalación y cómo listar y agregar registros. En una aplicación real también tendríamos que agregar gestión de usuarios, una página de login o validaciones JavaScript entre otras cosas, aunque es algo que sale del alcance de este tutorial.

Acciones

Antes de continuar, vamos a agregar una columna más a nuestra tabla que contenga dos enlaces para cada fila. Los enlaces enlazarán al archivo editar.php y al archivo borrar.php respectivamente. Para ello agregamos una cabecera adicional a la tabla:

<thead>
  <tr>
    <!-- ... -->
    <th>Acciones</th>
  </tr>
</thead>

También agregaremos una columna más con las acciones al cuerpo de la tabla:

<td>
  <a href="<?= 'borrar.php?id=' . escapar($fila["id"]) ?>">🗑️Borrar</a>
  <a href="<?= 'editar.php?id=' . escapar($fila["id"]) ?>">✏️Editar</a>
</td>

Tal y como ves, enviamos el id del usuario que queremos borrar o que queremos actualizar a las páginas borrar.php y editar.php respectivamente. Estas páginas todavía nos las hemos creado.

Este será el resultado:

 

Este es el código completo final del archivo index.php:

<?php
include 'funciones.php';

$error = false;
$config = include 'config.php';

try {
  $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
  $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);

  if (isset($_POST['apellido'])) {
    $consultaSQL = "SELECT * FROM alumnos WHERE apellido LIKE '%" . $_POST['apellido'] . "%'";
  } else {
    $consultaSQL = "SELECT * FROM alumnos";
  }

  $sentencia = $conexion->prepare($consultaSQL);
  $sentencia->execute();

  $resultados = $sentencia->fetchAll();

} catch(PDOException $error) {
  $error= $error->getMessage();
}

$titulo = isset($_POST['apellido']) ? 'Lista de alumnos (' . $_POST['apellido'] . ')' : 'Lista de alumnos';
?>

<?php include "templates/header.php"; ?>

<?php
if ($error) {
  ?>
  <div class="container mt-2">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-danger" role="alert">
          <?= $error ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <a href="crear.php"  class="btn btn-primary mt-4">Crear alumno</a>
      <hr>
      <form method="post" class="form-inline">
        <div class="form-group mr-3">
          <input type="text" id="apellido" name="apellido" placeholder="Buscar por apellido" class="form-control">
        </div>
        <button type="submit" name="submit" class="btn btn-primary">Ver resultados</button>
      </form>
    </div>
  </div>
</div>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <h2 class="mt-3"><?= $titulo ?></h2>
      <table class="table">
        <thead>
          <tr>
            <th>#</th>
            <th>Nombre</th>
            <th>Apellido</th>
            <th>Email</th>
            <th>Edad</th>
            <th>Acciones</th>
          </tr>
        </thead>
        <tbody>
          <?php
          if ($alumnos && $sentencia->rowCount() > 0) {
            foreach ($alumnos as $fila) {
              ?>
              <tr>
                <td><?php echo escapar($fila["id"]); ?></td>
                <td><?php echo escapar($fila["nombre"]); ?></td>
                <td><?php echo escapar($fila["apellido"]); ?></td>
                <td><?php echo escapar($fila["email"]); ?></td>
                <td><?php echo escapar($fila["edad"]); ?></td>
                <td>
                  <a href="<?= 'borrar.php?id=' . escapar($fila["id"]) ?>">🗑️Borrar</a>
                  <a href="<?= 'editar.php?id=' . escapar($fila["id"]) ?>" . >✏️Editar</a>
                </td>
              </tr>
              <?php
            }
          }
          ?>
        <tbody>
      </table>
    </div>
  </div>
</div>

<?php include "templates/footer.php"; ?>

CRUD (Update): Actualización de alumnos

Vamos a agregar una página que nos permita editar un usuario. Para ello crea el archivo editar.php en la carpeta raíz del proyecto con este contenido:

<?php require "templates/header.php"; ?>
<!-- código de la página -->
<?php require "templates/footer.php"; ?>

Necesitamos obtener los datos del usuario que estamos editando desde la base de datos y mostrar un formulario de edición.

Código PHP (Lectura)

Primero incluimos el archivo funciones.php y el array de configuración. Seguidamente comprobamos que el parámetro $_GET['id'] esté presente, mostrando un error en caso contrario. Luego nos conectamos a la base de datos para buscar el alumno que estamos editando.

Este es el código PHP que usamos para obtener el alumno que estamos editando, que va al principio del archivo editar.php:

<?php
include 'funciones.php';

$config = include 'config.php';

$resultado = [
  'error' => false,
  'mensaje' => ''
];

if (!isset($_GET['id'])) {
  $resultado['error'] = true;
  $resultado['mensaje'] = 'El alumno no existe';
}

try {
  $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
  $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);
    
  $id = $_GET['id'];
  $consultaSQL = "SELECT * FROM alumnos WHERE id =" . $id;

  $sentencia = $conexion->prepare($consultaSQL);
  $sentencia->execute();

  $alumno = $sentencia->fetch(PDO::FETCH_ASSOC);

  if (!$alumno) {
    $resultado['error'] = true;
    $resultado['mensaje'] = 'No se ha encontrado el alumno';
  }

} catch(PDOException $error) {
  $resultado['error'] = true;
  $resultado['mensaje'] = $error->getMessage();
}
?>

Tal y como ves, en caso de no pasar ningún id a la página o de que ocurra algún error, almacenamos el mensaje en el elemento $resultado['mensaje'].

Formulario HTML

A continuación debemos agregar un formulario con los datos actuales del alumno que hemos obtenido desde la base de datos, que están en el array $alumno. Este formulario es casi idéntico al que hemos agregado en el archivo crear.php, con la salvedad de que en este caso los campos tendrán un valor.

Sin embargo, primero debemos mostrar algún error en caso de que haya ocurrido alguno. Para ello usa el siguiente código después de la línea en la que incluimos el archivo header.php:

<?php
if ($resultado['error']) {
  ?>
  <div class="container mt-2">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-danger" role="alert">
          <?= $resultado['mensaje'] ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

En caso de que se haya enviado el formulario, tendremos que mostrar también un mensaje de confirmación siempre y cuando no haya habido errores:

<?php
if (isset($_POST['submit']) && !$resultado['error']) {
  ?>
  <div class="container mt-2">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-success" role="alert">
          El alumno ha sido actualizado correctamente
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

Seguidamente, tras los mensajes anteriores, agrega el código del formulario:

<?php
if (isset($alumno) && $alumno) {
  ?>
  <div class="container">
    <div class="row">
      <div class="col-md-12">
        <h2 class="mt-4">Editando el alumno <?= escapar($alumno['nombre']) . ' ' . escapar($alumno['apellido'])  ?></h2>
        <hr>
        <form method="post">
          <div class="form-group">
            <label for="nombre">Nombre</label>
            <input type="text" name="nombre" id="nombre" value="<?= escapar($alumno['nombre']) ?>" class="form-control">
          </div>
          <div class="form-group">
            <label for="apellido">Apellido</label>
            <input type="text" name="apellido" id="apellido" value="<?= escapar($alumno['apellido']) ?>" class="form-control">
          </div>
          <div class="form-group">
            <label for="email">Email</label>
            <input type="email" name="email" id="email" value="<?= escapar($alumno['email']) ?>" class="form-control">
          </div>
          <div class="form-group">
            <label for="edad">Edad</label>
            <input type="text" name="edad" id="edad" value="<?= escapar($alumno['edad']) ?>" class="form-control">
          </div>
          <div class="form-group">
            <input type="submit" name="submit" class="btn btn-primary" value="Actualizar">
            <a class="btn btn-primary" href="index.php">Regresar al inicio</a>
          </div>
        </form>
      </div>
    </div>
  </div>
  <?php
}
?>

Tal y como ves, solamente mostramos el formulario en caso de que se haya obtenido un alumno. Hemos usado el atributo value de los campos input HTML para definir el valor que tendrá cada campo.

Código PHP (Actualización)

Ahora que ya tenemos el formulario debemos definir las acciones a ejecutar cuando se envíe. Para ello, debemos detectar si el parámetro $_POST['submit'] está presente. Si es así, nos conectamos a la base de datos y actualizamos el alumno con los nuevos datos. Para completar esta tarea, agrega el siguiente código justo antes del bloque try/catch en el que obtenemos los datos del alumno:

if (isset($_POST['submit'])) {
  try {
    $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
    $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);
    
    // actualización del alumno

  } catch(PDOException $error) {
    $resultado['error'] = true;
    $resultado['mensaje'] = $error->getMessage();
  }
}

Ahora usaremos una sentencia SQL UPDATE para actualizar los valores del alumno cuyo id se corresponde con el que estamos editando:

UPDATE alumnos
  SET nombre = :nombre,
      apellido = :apellido,
      email = :email,
      edad = :edad
      updated_at = NOW()
  WHERE id = :id

Tal y como ves, también actualizamos el campo updated_at con la fecha actual, que obtenemos mediante la función NOW() de MySQL. Este sería el código PHP que implementa la consulta anterior:

$alumno = [
      "id"        => $_GET['id'],
      "nombre"    => $_POST['nombre'],
      "apellido"  => $_POST['apellido'],
      "email"     => $_POST['email'],
      "edad"      => $_POST['edad']
    ];
    
$consultaSQL = "UPDATE alumnos SET
  nombre = :nombre,
  apellido = :apellido,
  email = :email,
  edad = :edad,
  updated_at = NOW()
  WHERE id = :id";
    
$consulta = $conexion->prepare($consultaSQL);
$consulta->execute($alumno);

Y con esto ya habremos actualizado los datos del alumno.

Este sería el resultado cuando editas un alumno correctamente:

Este es el código completo final del archivo editar.php:

<?php
include 'funciones.php';

$config = include 'config.php';

$resultado = [
  'error' => false,
  'mensaje' => ''
];

if (!isset($_GET['id'])) {
  $resultado['error'] = true;
  $resultado['mensaje'] = 'El alumno no existe';
}

if (isset($_POST['submit'])) {
  try {
    $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
    $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);

    $alumno = [
      "id"        => $_GET['id'],
      "nombre"    => $_POST['nombre'],
      "apellido"  => $_POST['apellido'],
      "email"     => $_POST['email'],
      "edad"      => $_POST['edad']
    ];
    
    $consultaSQL = "UPDATE alumnos SET
        nombre = :nombre,
        apellido = :apellido,
        email = :email,
        edad = :edad,
        updated_at = NOW()
        WHERE id = :id";
    
    $consulta = $conexion->prepare($consultaSQL);
    $consulta->execute($alumno);

  } catch(PDOException $error) {
    $resultado['error'] = true;
    $resultado['mensaje'] = $error->getMessage();
  }
}

try {
  $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
  $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);
    
  $id = $_GET['id'];
  $consultaSQL = "SELECT * FROM alumnos WHERE id =" . $id;

  $sentencia = $conexion->prepare($consultaSQL);
  $sentencia->execute();

  $alumno = $sentencia->fetch(PDO::FETCH_ASSOC);

  if (!$alumno) {
    $resultado['error'] = true;
    $resultado['mensaje'] = 'No se ha encontrado el alumno';
  }

} catch(PDOException $error) {
  $resultado['error'] = true;
  $resultado['mensaje'] = $error->getMessage();
}
?>

<?php require "templates/header.php"; ?>

<?php
if ($resultado['error']) {
  ?>
  <div class="container mt-2">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-danger" role="alert">
          <?= $resultado['mensaje'] ?>
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

<?php
if (isset($_POST['submit']) && !$resultado['error']) {
  ?>
  <div class="container mt-2">
    <div class="row">
      <div class="col-md-12">
        <div class="alert alert-success" role="alert">
          El alumno ha sido actualizado correctamente
        </div>
      </div>
    </div>
  </div>
  <?php
}
?>

<?php
if (isset($alumno) && $alumno) {
  ?>
  <div class="container">
    <div class="row">
      <div class="col-md-12">
        <h2 class="mt-4">Editando el alumno <?= escapar($alumno['nombre']) . ' ' . escapar($alumno['apellido'])  ?></h2>
        <hr>
        <form method="post">
          <div class="form-group">
            <label for="nombre">Nombre</label>
            <input type="text" name="nombre" id="nombre" value="<?= escapar($alumno['nombre']) ?>" class="form-control">
          </div>
          <div class="form-group">
            <label for="apellido">Apellido</label>
            <input type="text" name="apellido" id="apellido" value="<?= escapar($alumno['apellido']) ?>" class="form-control">
          </div>
          <div class="form-group">
            <label for="email">Email</label>
            <input type="email" name="email" id="email" value="<?= escapar($alumno['email']) ?>" class="form-control">
          </div>
          <div class="form-group">
            <label for="edad">Edad</label>
            <input type="text" name="edad" id="edad" value="<?= escapar($alumno['edad']) ?>" class="form-control">
          </div>
          <div class="form-group">
            <input type="submit" name="submit" class="btn btn-primary" value="Actualizar">
            <a class="btn btn-primary" href="index.php">Regresar al inicio</a>
          </div>
        </form>
      </div>
    </div>
  </div>
  <?php
}
?>

<?php require "templates/footer.php"; ?>

CRUD (Delete): Borrado de alumnos

Vamos a agregar una página que nos permita borrar un alumno de la base de datos. Para ello crea el archivo borrar.php en la carpeta raíz del proyecto con este contenido:

<?php require "templates/header.php"; ?>
<!-- código de la página -->
<?php require "templates/footer.php"; ?>

Lo que haremos será ejecutar una consulta que borre el alumno cuyo id se corresponde con el parámetro $_GET['id']. Si el alumno es borrado con éxito redirigiremos al usuario a la página index.php. De lo contrario mostraremos un mensaje de e error.

Código PHP

Primero vamos a conectarnos a la base de datos y a borrar el alumno, para luego usar la función header para redirigir al usuario al archivo index.php. Agrega este código antes de la línea en la que incluimos el archivo header.php:

<?php
include 'funciones.php';

$config = include 'config.php';

$resultado = [
  'error' => false,
  'mensaje' => ''
];

try {
  $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
  $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);
    
  $id = $_GET['id'];
  $consultaSQL = "DELETE FROM alumnos WHERE id =" . $id;

  $sentencia = $conexion->prepare($consultaSQL);
  $sentencia->execute();

  header('Location: /index.php');

} catch(PDOException $error) {
  $resultado['error'] = true;
  $resultado['mensaje'] = $error->getMessage();
}
?>

Código HTML

El siguiente código HTML solamente se mostrará si ha ocurrido algún error. Agrega este código después de la línea donde incluimos el archivo header.php:

<div class="container mt-2">
  <div class="row">
    <div class="col-md-12">
      <div class="alert alert-danger" role="alert">
        <?= $resultado['mensaje'] ?>
      </div>
    </div>
  </div>
</div>

Y con esto ya hemos terminado. Este sería el código completo final del archivo borrar.php:

<?php
include 'funciones.php';

$config = include 'config.php';

$resultado = [
  'error' => false,
  'mensaje' => ''
];

try {
  $dsn = 'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['name'];
  $conexion = new PDO($dsn, $config['db']['user'], $config['db']['pass'], $config['db']['options']);
    
  $id = $_GET['id'];
  $consultaSQL = "DELETE FROM alumnos WHERE id =" . $id;

  $sentencia = $conexion->prepare($consultaSQL);
  $sentencia->execute();

  header('Location: /index.php');

} catch(PDOException $error) {
  $resultado['error'] = true;
  $resultado['mensaje'] = $error->getMessage();
}
?>

<?php require "templates/header.php"; ?>

<div class="container mt-2">
  <div class="row">
    <div class="col-md-12">
      <div class="alert alert-danger" role="alert">
        <?= $resultado['mensaje'] ?>
      </div>
    </div>
  </div>
</div>

<?php require "templates/footer.php"; ?>

Protección CSRF

Vamos a agregar protección contra ataques CSRF, mediante los cuales un atacante puede engañar al navegador y ejecutar código no deseado. Lo que haremos será almacenar un token CSRF en una variable de sesión del servidor. Validaremos el token del servidor contra un valor que agregaremos en un campo oculto de los formulario de la aplicación.

Edita el archivo funciones.php y agrega esta función, en la que generamos un token que almacenamos en la variable de sesión $_SESSION['csrf']:

function csrf() {

  session_start();

  if (empty($_SESSION['csrf'])) {
    if (function_exists('random_bytes')) {
      $_SESSION['csrf'] = bin2hex(random_bytes(32));
    } else if (function_exists('mcrypt_create_iv')) {
      $_SESSION['csrf'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
    } else {
      $_SESSION['csrf'] = bin2hex(openssl_random_pseudo_bytes(32));
    }
  }
}

Puede que no estén disponibles ciertas funciones en tu sistema, por lo que intentamos generar el token de sesión con varias de las funciones más habituales.

Luego, agrega este código tras incluir el archivo funciones.php en todas las páginas de la aplicación.

include 'funciones.php';
csrf();

if (isset($_POST['submit']) && !hash_equals($_SESSION['csrf'], $_POST['csrf'])) {
  die();
}

Lo que hemos hecho es comprobar que el token CSRF de sesión sea igual que el que agregaremos en el formulario. Si no es igual, finalizamos la ejecución de la aplicación.

Luego, agrega este campo oculto a los formularios de la aplicación:

<input name="csrf" type="hidden" value="<?php echo escapar($_SESSION['csrf']); ?>">

Al usar el atributo hidden, el campo se mantendrá oculto.

Conclusión

Y con esto ya habrás creado tu primera aplicació CRUD. Es más que recomendable saber programar correctamente con PHP antes de aventurarse en el uso de frameworks aunque a priori pueda paracer que usarlos es más sencillo. De este modo, tendrás una idea más clara de lo que ocurre.

Esta aplicación todavía necesitaría más elementos para ser segura y poder ser usada en producción, pero es una introducción que te resultará muy útil.

Recuerda que puedes consultar el código completo de la aplicación CRUD en GitHub.

Esto ha sido todo.


Avatar de Edu Lazaro

Edu Lázaro: Ingeniero técnico en informática, actualmente trabajo como desarrollador web y programador de videojuegos.

👋 Hola! Soy Edu, me encanta crear cosas y he redactado esta guía. Si te ha resultado útil, el mayor favor que me podrías hacer es el de compatirla en Twitter 😊

Si quieres conocer mis proyectos, sígueme en Twitter.

35 comentarios en “Cómo crear una aplicación CRUD con PHP y MySQL

  1. Amigo un fuerte abrazo, no te imaginas lo que he googleado, llevo una semana y todo lo que ví de crear un CRUD con ninguna explicacion habia podido entenderla. Ni a mi profesor, jajaja pero Sobre todo por que siempre se saltan pasos o hacen el codigo muy poco entendible, pero tengo muchasss cosas que agradecerte Y el explicar lo que hace cada linea, eso tambien es maravilloso, pero hay algo que fue lo que más me sorprendio de este post y es :
    $consultaSQL = «INSERT INTO persona (ID_PERSONA,PRIMER_NOMB,SEGUNDO_NOMB,PRIMER_APELL,SEGUNDO_APELL,DIRECCION,NUMERO_TELEFONO,EMAIL)»;
    $consultaSQL .= «VALUES (:» . implode(«, :», array_keys($alumno)) . «)»;

    este trozo de código tan simple pero tan fácil de aprender, para insertar todos los campos en una tabla es realmente fenomenal..no esta estructurado como siempre lo vez en internet, en serio hermano Mil gracias Mil…Desde Mi Colombia del Alma un Abrazo PARCERO!!!!…
    PD: Sigue cambiando el Mundo!!!

      1. Pues lo has logrado. Tengo casi un año buscando un tutorial que aclarara, no que confundiera. Está muy completo, y por fin sé lo que estoy haciendo cada vez que uso el CRUD. Sigue haciendo tutoriales, te seguiremos y compartiremos.

  2. EXCELENTE Aporte para los que apenas comenzamos, pero como?, ya teniendo este proyecto pudiera publicarlo en algún host con dominio de los gratuitos, me atrevo a solicitarte un paso paso ya que eres uno de los pocos que las explicaciones se entienden a las mil maravillas, y con esto poder llevar a otro nivel este gran tuturial de CRUD en php.
    Else { por lo menos tenerlo en cuenta para futuros tutos, por que sabes que la expresión MAXIMA de hacer estos proyectos web es verlos publicados.}
    GRACIAS!!!! COMPADRE

  3. Muchas gracias desde Cuba. No había encontrado un tutorial tan completo en internet. Gracias, he aprendido un monton con su dominio en la materia. Muchos cobran por enseñar y usted tiene la virtud de compartir y enseñar de forma gratuita sus conocimientos, Gracias por su altruismo.

  4. Muy buen tutorial, lo estoy haciendo y no quise hacer solo copiar y pegar, sino ir analizando bien el codigo con las explicaciones. Creo que hay un error en la parte que dice «Este es el código completo final del archivo index.php:» en la linea de codigo:
    $resultados = $sentencia->fetchAll();
    en donde en lugar de $resultados debe ir la variable $alumnos, o sea:
    $alumnos = $sentencia->fetchAll();
    por lo menos a mi de la otra forma no me resultaba y al ponerme a descubrir porqué no funcionaba vi que la que va es $alumnos
    Puede ser? Capaz que al final lo aclara, pero todavía no he llegado al final porque como dije, voy intentando comprender el código 😅
    Gracias por tu tiempo

  5. Hola Edu.

    Antes de nada, estoy empezando a aprender php y creo que te acostumbras a lo que te enseñan, y si te lo enseñan mal te cuesta el triple aprender, desaprender y aprender de nuevo.

    Me encanta tu código y las buenas prácticas que aplicas. Incluso me parece que haces que php sea bonito de ver. Tal vez sea la percepción de un principiante.

    Tenía una pregunta chorra que me llama mucho la atención. ¿Por qué haces con comillas simples y el require con dobles?

    La verdad es que con php pierdo demasiado tiempo intentando resolver dudas chorras como ésta para acostumbrarme a un buen estilo y prácticas desde el principio. Me da la impresión que, con tantos recursos como hay en php, es todo muy anárquico. Esto no pasa en otros lenguajes.

  6. hola! realmente me ha encantado estoy probando para hacer en mi colegio uno, pero tengo dos dudas,

    1) si quisiera que cuando doy de alta un alumno, pudiera a su vez, tocar un botón, donde me permita agregarle al alumno un detalle, por ejemplo, de una lista desplegable, que me permita seleccionar » comportamiento:» y se pueda elegir «bueno, muy bueno, malo.. etc… de una lista desplegable la cual viene de una tabla llamada «conducta», y su columnas «id» y «detalle» donde ahi dice, id- 1- detalle: bueno. id 2.- detalle: malo. etc.

    2) que permita ingresar alumnos ya cargados en una base de dato, donde «alumnos» y en las columnas» ID, DNI, SEXO, NOMBREYAPELLIDO». y si no esta, me permita ingresarlo a mano. gracias.

  7. Edu, muchas gracias por esa excelente metodología, es muy clara y fácil de entender, he ido realizando la actividad a medida que voy leyendo y ha sido en verdad muy instructivo y motivante para mi.
    Tengo una duda, ¿si tengo varios CRUD para hacer, como debo organizar los archivos?, disculpa mi ignorancia, pero apenas estoy empezando en este nuevo mundo web.
    Nuevamente MUCHAS GRACIAS.

  8. Muchas gracias Edu por la excelente guía para aprender a hacer CRUD.

    Por si sirve, en el punto Ataques XSS, cuando se carga la primer función , deberías agregarle la apertura al PHP de la función. Es obvio para los que ya saben pero no se si para los novatos.

  9. Estimado Edu
    Muchas gracias por la contribución. Fue muy didáctica la explicación.
    Tengo una consulta la cual en la realidad es un complemento a lo ya explicado.
    En la tabla original Alumnos se ha considerado el campo ‘created_at’ el cual no se usa en todas las rutinas explicadas. Como su nombre lo indica, es una campo para almacenar la fecha de creación del registro. Al respecto propongo añadir las siguientes líneas de instrucción:
    $hoy = NOW();
    $last_id = $conexion->lastInserted();
    $consultaSQL2 = «UPDATE Alumnos SET created_at = «.$hoy.» WHERE id = «.$last_id.» «;
    $query_success = $conexion->query($consultaSQL2);

    De todas formas, va a ser interesante conocer tu apreciación sobre las líneas de código escritas.
    Atentamente,
    Fernando Díaz

    1. Buenas Fernando
      Si te fijas en como esta creada la tabla en MySQL pone
      » created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,»
      es decir por defecto le pone el valor current_timestamp o valor de fecha actual.
      No hace falta hacer nada desde el codigo.
      La diferencia con el update es que puedes tener varios update en el tiempo y ahi si tienes que cambiar el valor

  10. Muchas gracias por el tutorial, muy bueno.

    Tengo una duda, es posible en el código php ejecutar un query que tenga tablas temporales?, quiero que desde la pagina web se visualice un reporte, y este lo tengo en query. He tratado pero me sale error.

    O debo crear un procedimiento almacenado para invocarlo.

    Gracias,

  11. hola ammm si todo lo hisiste en el index para que los demas documentos pense que arias un menu en el index y buscarias la funcin de cada boton!!!
    y por que lamas tantas vese a la conexion de la db ???
    muchas lineas lo hace un poco complicado es solo una observacion que pena.

    pero aun asi aprendi aserca de pdo gracias !!!

  12. Me parece muy util, aunque en el formulario de crear deberia tener un campo con select que muestre dos campos dentro del mismo formulario, ejemplo select codigo, ciudad de la tabla ciudades, mostrara codigo y ciudad. Necesito codigo fuente para otro programa, puedo contar con usted, precio. muy amable, gracias

  13. Buenisimo !!! Tengo mas de una semana viendo tutoriales y entendia muy poco o casi nada, vi un curso en youtube y empece a entender mas, tu lo llevaste a lo que necesitaba de la manera mas clara, solo me falta implementarlo en kubernetes y termine una tarea para la clase. Muchas gracias.

  14. Saludos. Excelente trabajo.
    Felicidades Edu Lazaro.

    Quisiera consultarte algo si es posible en la parte de editar ya que no me esta funcionando y me ha arrojado error al momento de dar clic sobre el registro a editar

  15. Hola, en la parte de busqueda que dice:
    1.El formulario enviará el apellido del alumno que introduzcamos a la propia página, por lo que debemos agregar una consulta SQL… ¿donde agrego el codigo?:
    if (isset($_POST[‘apellido’])) {
    $consultaSQL = «SELECT * FROM alumnos WHERE apellido LIKE ‘%» . $_POST[‘apellido’] . «%'»;
    } else {
    $consultaSQL = «SELECT * FROM alumnos»;
    }

    2. tambien donde dice: Vamos a modificar también el título del formulario ¿donde agrego el codigo?:
    $titulo = isset($_POST[‘apellido’]) ? ‘Lista de alumnos (‘ . $_POST[‘apellido’] . ‘)’ : ‘Lista de alumnos’;

    ¿Alguien me ayuda?

  16. Un Millón de Gracias amigo me Funciono perfectamente mejor tutorial que pude ver que te aclara las dudas en vez de ocasionarte mas, Tenia 3 semanas tratando de conseguir uno que me explicara lo que esta en este y que fuera especifico pero no encontré ninguno solo este y lo que otros no pudieron explicar con palabras vos lo hiciste con texto lo que requiere de paciencia y dedicación, millones de Gracias ya que de eso dependia una parte de mi proyecto de la uni, necesitaba la implementación de un crud pero no me gusta el copia y pega sin ninguna explicación porque uno no entiende que fue lo que hizo pero este no entro en esa categoría saludos amigo desde Venezuela.

Deja una respuesta

“- Hey, Doc. No tenemos suficiente carretera para ir a 140/h km. - ¿Carretera? A donde vamos, no necesitaremos carreteras.”