CRUD completo con PHP, MySQL y PDO: crear, listar, editar, eliminar y buscar

Ya vimos cómo conectar PHP con MySQL mediante PDO, cómo leer datos y cómo ejecutar INSERT, UPDATE y DELETE.

Ahora vamos a integrar todo eso en una pequeña aplicación funcional. Construiremos un CRUD completo de usuarios con listado, búsqueda, alta, edición y eliminación.

CRUD es el acrónimo de:

Create  → crear
Read    → leer
Update  → actualizar
Delete  → eliminar

Para profundizar en SQL: en este proyecto utilizamos SELECT, WHERE, LIKE, INSERT, UPDATE y DELETE. La teoría de esas sentencias está desarrollada en nuestra ruta de aprendizaje SQL / MySQL.

Qué vamos a construir

La aplicación tendrá estas pantallas:

  • index.php: listado y búsqueda de usuarios;
  • crear.php: alta de usuarios;
  • editar.php: modificación;
  • eliminar.php: eliminación mediante POST;
  • conexion.php: conexión centralizada mediante PDO.

Estructura del proyecto

crud-usuarios/
│
├── conexion.php
├── index.php
├── crear.php
├── editar.php
└── eliminar.php

Base de datos

Seguiremos usando la base club_programador y la tabla usuarios:

CREATE TABLE usuarios (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    edad INT
);

1. conexion.php

Centralizamos la conexión para no repetirla en cada archivo.

<?php

$host = 'localhost';
$base = 'club_programador';
$usuario = 'root';
$password = '';

$dsn =
    "mysql:host=$host;dbname=$base;charset=utf8mb4";

try {

    $pdo = new PDO(
        $dsn,
        $usuario,
        $password,
        [
            PDO::ATTR_ERRMODE =>
                PDO::ERRMODE_EXCEPTION,

            PDO::ATTR_DEFAULT_FETCH_MODE =>
                PDO::FETCH_ASSOC
        ]
    );

} catch (PDOException $e) {

    exit(
        'No se pudo conectar a la base de datos'
    );
}

2. index.php: listado y buscador

La página principal mostrará todos los usuarios y permitirá buscar por nombre o email.

<?php

require 'conexion.php';

$busqueda = trim($_GET['q'] ?? '');

if ($busqueda === '') {

    $sql =
        'SELECT id, nombre, email, edad
         FROM usuarios
         ORDER BY nombre';

    $stmt = $pdo->query($sql);

} else {

    $sql =
        'SELECT id, nombre, email, edad
         FROM usuarios
         WHERE nombre LIKE :busqueda
            OR email LIKE :busqueda
         ORDER BY nombre';

    $stmt = $pdo->prepare($sql);

    $stmt->execute([
        'busqueda' => '%' . $busqueda . '%'
    ]);
}

$usuarios = $stmt->fetchAll();

$mensaje = $_GET['mensaje'] ?? '';

?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Usuarios</title>
</head>
<body>

<h1>Usuarios</h1>

<p>
    <a href="crear.php">
        Crear usuario
    </a>
</p>

<?php if ($mensaje === 'creado'): ?>
    <p>Usuario creado correctamente.</p>
<?php elseif ($mensaje === 'actualizado'): ?>
    <p>Usuario actualizado correctamente.</p>
<?php elseif ($mensaje === 'eliminado'): ?>
    <p>Usuario eliminado correctamente.</p>
<?php endif; ?>

<form method="get">

    <input
        type="text"
        name="q"
        placeholder="Buscar..."
        value="<?php echo htmlspecialchars(
            $busqueda,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <button type="submit">
        Buscar
    </button>

</form>

<hr>

<?php if (empty($usuarios)): ?>

    <p>No se encontraron usuarios.</p>

<?php else: ?>

    <table border="1" cellpadding="8">

        <thead>
        <tr>
            <th>ID</th>
            <th>Nombre</th>
            <th>Email</th>
            <th>Edad</th>
            <th>Acciones</th>
        </tr>
        </thead>

        <tbody>

        <?php foreach ($usuarios as $usuario): ?>

            <tr>

                <td>
                    <?php echo $usuario['id']; ?>
                </td>

                <td>
                    <?php echo htmlspecialchars(
                        $usuario['nombre'],
                        ENT_QUOTES,
                        'UTF-8'
                    ); ?>
                </td>

                <td>
                    <?php echo htmlspecialchars(
                        $usuario['email'],
                        ENT_QUOTES,
                        'UTF-8'
                    ); ?>
                </td>

                <td>
                    <?php echo htmlspecialchars(
                        $usuario['edad'],
                        ENT_QUOTES,
                        'UTF-8'
                    ); ?>
                </td>

                <td>

                    <a href="editar.php?id=<?php
                        echo $usuario['id'];
                    ?>">
                        Editar
                    </a>

                    <form
                        method="post"
                        action="eliminar.php"
                        style="display:inline"
                        onsubmit="return confirm(
                            '¿Eliminar este usuario?'
                        );"
                    >

                        <input
                            type="hidden"
                            name="id"
                            value="<?php
                                echo $usuario['id'];
                            ?>"
                        >

                        <button type="submit">
                            Eliminar
                        </button>

                    </form>

                </td>

            </tr>

        <?php endforeach; ?>

        </tbody>

    </table>

<?php endif; ?>

</body>
</html>

Qué hace index.php

La página combina varias cosas que ya aprendimos:

  • recibe un filtro mediante GET;
  • usa una consulta fija cuando no existe búsqueda;
  • usa una consulta preparada cuando existe un valor dinámico;
  • recupera los resultados con fetchAll();
  • recorre los registros con foreach;
  • escapa los datos antes de mostrarlos en HTML;
  • ofrece acciones de editar y eliminar.

Para profundizar en SQL: en este proyecto utilizamos SELECT, WHERE, LIKE, INSERT, UPDATE y DELETE. La teoría de esas sentencias está desarrollada en nuestra ruta de aprendizaje SQL / MySQL.

3. crear.php

Esta pantalla permitirá agregar usuarios nuevos.

<?php

require 'conexion.php';

$nombre = '';
$email = '';
$edad = '';

$errores = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $nombre = trim($_POST['nombre'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $edad = trim($_POST['edad'] ?? '');

    if ($nombre === '') {
        $errores[] =
            'El nombre es obligatorio';
    }

    if (!filter_var(
        $email,
        FILTER_VALIDATE_EMAIL
    )) {
        $errores[] =
            'El email no es válido';
    }

    if (
        $edad !== '' &&
        filter_var(
            $edad,
            FILTER_VALIDATE_INT
        ) === false
    ) {
        $errores[] =
            'La edad debe ser un número entero';
    }

    if (empty($errores)) {

        $sql =
            'INSERT INTO usuarios
                (nombre, email, edad)
             VALUES
                (:nombre, :email, :edad)';

        $stmt = $pdo->prepare($sql);

        $stmt->execute([
            'nombre' => $nombre,
            'email' => $email,
            'edad' =>
                $edad === ''
                ? null
                : (int) $edad
        ]);

        header(
            'Location: index.php?mensaje=creado'
        );

        exit;
    }
}

?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Crear usuario</title>
</head>
<body>

<h1>Crear usuario</h1>

<?php if (!empty($errores)): ?>

    <ul>
        <?php foreach ($errores as $error): ?>
            <li>
                <?php echo htmlspecialchars(
                    $error,
                    ENT_QUOTES,
                    'UTF-8'
                ); ?>
            </li>
        <?php endforeach; ?>
    </ul>

<?php endif; ?>

<form method="post">

    <label for="nombre">
        Nombre:
    </label>

    <input
        type="text"
        id="nombre"
        name="nombre"
        value="<?php echo htmlspecialchars(
            $nombre,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <br><br>

    <label for="email">
        Email:
    </label>

    <input
        type="email"
        id="email"
        name="email"
        value="<?php echo htmlspecialchars(
            $email,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <br><br>

    <label for="edad">
        Edad:
    </label>

    <input
        type="number"
        id="edad"
        name="edad"
        value="<?php echo htmlspecialchars(
            $edad,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <br><br>

    <button type="submit">
        Guardar
    </button>

    <a href="index.php">
        Cancelar
    </a>

</form>

</body>
</html>

Post/Redirect/Get en el alta

Después de insertar el usuario no mostramos directamente una página de éxito.

En cambio hacemos:

header(
    'Location: index.php?mensaje=creado'
);

exit;

Esto evita que el navegador repita el INSERT si el usuario actualiza la página.

4. editar.php

La edición tiene dos etapas:

GET  → cargar el usuario
POST → validar y actualizar

Primero obtenemos y validamos el ID.

$id = filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT
);

if ($id === false || $id === null) {
    exit('ID no válido');
}

Cargar el usuario

$sql =
    'SELECT id, nombre, email, edad
     FROM usuarios
     WHERE id = :id';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'id' => $id
]);

$usuario = $stmt->fetch();

if (!$usuario) {
    exit('Usuario no encontrado');
}

editar.php completo

<?php

require 'conexion.php';

$id = filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT
);

if ($id === false || $id === null) {
    exit('ID no válido');
}

$stmt = $pdo->prepare(
    'SELECT id, nombre, email, edad
     FROM usuarios
     WHERE id = :id'
);

$stmt->execute([
    'id' => $id
]);

$usuario = $stmt->fetch();

if (!$usuario) {
    exit('Usuario no encontrado');
}

$nombre = $usuario['nombre'];
$email = $usuario['email'];
$edad = $usuario['edad'];

$errores = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $nombre = trim($_POST['nombre'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $edad = trim($_POST['edad'] ?? '');

    if ($nombre === '') {
        $errores[] =
            'El nombre es obligatorio';
    }

    if (!filter_var(
        $email,
        FILTER_VALIDATE_EMAIL
    )) {
        $errores[] =
            'El email no es válido';
    }

    if (
        $edad !== '' &&
        filter_var(
            $edad,
            FILTER_VALIDATE_INT
        ) === false
    ) {
        $errores[] =
            'La edad no es válida';
    }

    if (empty($errores)) {

        $sql =
            'UPDATE usuarios
             SET nombre = :nombre,
                 email = :email,
                 edad = :edad
             WHERE id = :id';

        $stmt = $pdo->prepare($sql);

        $stmt->execute([
            'nombre' => $nombre,
            'email' => $email,
            'edad' =>
                $edad === ''
                ? null
                : (int) $edad,
            'id' => $id
        ]);

        header(
            'Location: index.php?mensaje=actualizado'
        );

        exit;
    }
}

?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Editar usuario</title>
</head>
<body>

<h1>Editar usuario</h1>

<?php if (!empty($errores)): ?>

    <ul>
        <?php foreach ($errores as $error): ?>
            <li>
                <?php echo htmlspecialchars(
                    $error,
                    ENT_QUOTES,
                    'UTF-8'
                ); ?>
            </li>
        <?php endforeach; ?>
    </ul>

<?php endif; ?>

<form method="post">

    <label for="nombre">
        Nombre:
    </label>

    <input
        type="text"
        id="nombre"
        name="nombre"
        value="<?php echo htmlspecialchars(
            $nombre,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <br><br>

    <label for="email">
        Email:
    </label>

    <input
        type="email"
        id="email"
        name="email"
        value="<?php echo htmlspecialchars(
            $email,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <br><br>

    <label for="edad">
        Edad:
    </label>

    <input
        type="number"
        id="edad"
        name="edad"
        value="<?php echo htmlspecialchars(
            $edad,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <br><br>

    <button type="submit">
        Guardar cambios
    </button>

    <a href="index.php">
        Cancelar
    </a>

</form>

</body>
</html>

5. eliminar.php

La eliminación se ejecutará solamente mediante POST.

<?php

require 'conexion.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit('Método no permitido');
}

$id = filter_input(
    INPUT_POST,
    'id',
    FILTER_VALIDATE_INT
);

if ($id === false || $id === null) {
    exit('ID no válido');
}

$stmt = $pdo->prepare(
    'DELETE FROM usuarios
     WHERE id = :id'
);

$stmt->execute([
    'id' => $id
]);

header(
    'Location: index.php?mensaje=eliminado'
);

exit;

Por qué DELETE usa POST

Eliminar información cambia el estado de la aplicación. No debería ocurrir simplemente al visitar una URL.

Por eso en index.php utilizamos:

<form
    method="post"
    action="eliminar.php"
>

    <input
        type="hidden"
        name="id"
        value="<?php echo $usuario['id']; ?>"
    >

    <button type="submit">
        Eliminar
    </button>

</form>

Flujo completo del CRUD

LISTAR
index.php
    ↓
SELECT
    ↓
tabla HTML


CREAR
crear.php
    ↓
formulario POST
    ↓
validación
    ↓
INSERT
    ↓
redirect a index.php


EDITAR
editar.php?id=5
    ↓
SELECT
    ↓
formulario precargado
    ↓
POST
    ↓
UPDATE
    ↓
redirect


ELIMINAR
index.php
    ↓
formulario POST
    ↓
eliminar.php
    ↓
DELETE
    ↓
redirect

Buscar forma parte del Read

En un CRUD real, leer datos no significa solamente listar todos los registros.

También suele incluir:

  • búsqueda;
  • filtros;
  • ordenamiento;
  • paginación;
  • vista de detalle.

En nuestro ejemplo incorporamos búsqueda mediante LIKE.

Para profundizar en SQL: en este proyecto utilizamos SELECT, WHERE, LIKE, INSERT, UPDATE y DELETE. La teoría de esas sentencias está desarrollada en nuestra ruta de aprendizaje SQL / MySQL.

Separar responsabilidades

Aunque esta estructura es correcta para aprender, vemos que crear.php y editar.php comienzan a tener bastante código.

En proyectos mayores normalmente separamos:

acceso a datos
validación
lógica de negocio
controladores
vistas

Ese tipo de organización nos prepara para comprender posteriormente frameworks como Laravel.

Un posible siguiente paso: reutilizar el formulario

Crear y editar contienen HTML muy parecido.

Podríamos extraerlo a un archivo común, por ejemplo:

formulario.php

y reutilizarlo desde ambas pantallas.

Ese tipo de mejoras reduce duplicación, aunque en esta guía mantuvimos los archivos completos para que pueda entenderse cada flujo por separado.

Seguridad: lo que ya estamos haciendo

Nuestro CRUD aplica varias prácticas importantes:

  • consultas preparadas;
  • validación de formularios;
  • validación de IDs;
  • escapado de salida con htmlspecialchars();
  • POST para eliminaciones;
  • Post/Redirect/Get.

Lo que todavía falta en una aplicación real

Este CRUD es didáctico. Un sistema de producción todavía debería considerar, entre otras cosas:

  • autenticación de usuarios;
  • autorización y permisos;
  • protección CSRF;
  • registro controlado de errores;
  • validaciones de negocio más completas;
  • manejo de duplicados, por ejemplo emails únicos;
  • paginación para grandes cantidades de registros;
  • separación más clara de responsabilidades.

Qué ocurre si el email ya existe

La base tiene una restricción UNIQUE sobre email. Si intentamos insertar un email duplicado, MySQL rechazará la operación.

Esto muestra algo importante: la validación de PHP y las restricciones de la base de datos trabajan juntas.

La aplicación puede validar primero, pero la base debe seguir protegiendo la integridad de los datos.

Mejoras propuestas para practicar

  • agregar una página de detalle;
  • agregar teléfono;
  • agregar estado activo/inactivo;
  • permitir ordenar por nombre;
  • agregar filtro por edad;
  • agregar paginación;
  • mejorar el diseño con CSS;
  • reutilizar el formulario de crear/editar;
  • manejar el error de email duplicado;
  • agregar un campo de fecha de creación.

Ejercicios

  1. crear los cinco archivos del proyecto;
  2. probar el listado sin usuarios;
  3. crear tres usuarios;
  4. buscar por nombre;
  5. buscar por email;
  6. editar un usuario;
  7. eliminar un usuario;
  8. comprobar que actualizar la página no repite una operación POST;
  9. mostrar un mensaje cuando una búsqueda no devuelve resultados;
  10. agregar una página de detalle;
  11. agregar un campo nuevo a la tabla y a todos los formularios;
  12. implementar una mejora propia al CRUD.

Qué aprendimos

  • qué significa CRUD;
  • cómo organizar un CRUD sencillo en varios archivos;
  • cómo listar datos desde MySQL;
  • cómo implementar búsqueda;
  • cómo crear registros;
  • cómo precargar y editar registros;
  • cómo eliminar mediante POST;
  • cómo utilizar Post/Redirect/Get;
  • cómo integrar formularios, PDO, MySQL y HTML;
  • qué diferencias existen entre un ejemplo didáctico y una aplicación de producción.

Continuá aprendiendo SQL / MySQL

Este CRUD utiliza varias consultas SQL, pero el objetivo de esta guía es integrarlas con PHP. Para profundizar en consultas, filtros, relaciones, índices, normalización y otros temas de base de datos:

👉 Ver la ruta completa de SQL / MySQL

¿Qué sigue?

Después de construir nuestro primer CRUD, el siguiente paso natural es aprender a trabajar con sesiones y autenticación en PHP: iniciar sesión, mantener un usuario autenticado, cerrar sesión y proteger páginas privadas.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

INSERT, UPDATE y DELETE con PHP y PDO: guardar, editar y eliminar datos

En la guía anterior conectamos PHP con MySQL utilizando PDO y aprendimos a leer datos con SELECT.

Ahora vamos a avanzar con las operaciones que permiten crear, modificar y eliminar registros. Es decir, vamos a empezar a trabajar con la parte operativa de una aplicación real.

¿Querés profundizar en la parte SQL?

En esta guía vamos a utilizar INSERT, UPDATE, DELETE y WHERE. La teoría completa de estas sentencias está desarrollada en nuestra ruta de aprendizaje SQL / MySQL.

Las tres operaciones que veremos

  • INSERT: crear registros nuevos;
  • UPDATE: modificar registros existentes;
  • DELETE: eliminar registros.

Las tres operaciones se realizarán con consultas preparadas mediante PDO.

Tabla de ejemplo

Seguimos trabajando con la tabla usuarios:

usuarios
-------------------------
id
nombre
email
edad

Insertar un usuario

La consulta SQL básica sería:

INSERT INTO usuarios (nombre, email, edad)
VALUES ('Ana', 'ana@email.com', 25);

Pero desde PHP no debemos concatenar directamente valores recibidos del usuario.

Utilizamos placeholders:

$sql =
    'INSERT INTO usuarios
        (nombre, email, edad)
     VALUES
        (:nombre, :email, :edad)';

$stmt = $pdo->prepare($sql);

Ejecutar el INSERT

$stmt->execute([
    'nombre' => $nombre,
    'email' => $email,
    'edad' => $edad
]);

Formulario de alta

<form method="post">

    <label for="nombre">Nombre:</label>
    <input
        type="text"
        id="nombre"
        name="nombre"
    >

    <br><br>

    <label for="email">Email:</label>
    <input
        type="email"
        id="email"
        name="email"
    >

    <br><br>

    <label for="edad">Edad:</label>
    <input
        type="number"
        id="edad"
        name="edad"
    >

    <br><br>

    <button type="submit">
        Guardar
    </button>

</form>

Procesar el formulario

<?php

require 'conexion.php';

$errores = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $nombre = trim($_POST['nombre'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $edad = trim($_POST['edad'] ?? '');

    if ($nombre === '') {
        $errores[] = 'El nombre es obligatorio';
    }

    if (!filter_var(
        $email,
        FILTER_VALIDATE_EMAIL
    )) {
        $errores[] = 'El email no es válido';
    }

    if ($edad === '' || !is_numeric($edad)) {
        $errores[] = 'La edad no es válida';
    }

    if (empty($errores)) {

        $sql =
            'INSERT INTO usuarios
                (nombre, email, edad)
             VALUES
                (:nombre, :email, :edad)';

        $stmt = $pdo->prepare($sql);

        $stmt->execute([
            'nombre' => $nombre,
            'email' => $email,
            'edad' => $edad
        ]);

        echo 'Usuario guardado correctamente';
    }
}

Recuperar el ID insertado

Si la tabla utiliza una clave autoincremental, PDO puede devolver el último ID generado:

$id = $pdo->lastInsertId();

echo 'Nuevo usuario: ' . $id;

Esto puede ser útil si después queremos redirigir al detalle del registro recién creado.

Modificar un registro con UPDATE

Supongamos que queremos cambiar nombre y email de un usuario.

$sql =
    'UPDATE usuarios
     SET nombre = :nombre,
         email = :email,
         edad = :edad
     WHERE id = :id';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'nombre' => $nombre,
    'email' => $email,
    'edad' => $edad,
    'id' => $id
]);

¿Querés profundizar en la parte SQL?

En esta guía vamos a utilizar INSERT, UPDATE, DELETE y WHERE. La teoría completa de estas sentencias está desarrollada en nuestra ruta de aprendizaje SQL / MySQL.

¿Por qué WHERE es fundamental en UPDATE?

La cláusula WHERE indica qué registro debe modificarse.

Una consulta como:

UPDATE usuarios
SET nombre = 'Ana';

podría modificar todos los registros de la tabla.

Por eso, antes de ejecutar un UPDATE, debemos revisar con especial cuidado la condición utilizada.

Editar un usuario: primer paso

Normalmente recibimos el ID mediante GET:

editar.php?id=5

Lo validamos:

$id = filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT
);

if ($id === false || $id === null) {
    exit('ID no válido');
}

Buscar el usuario antes de editar

$sql =
    'SELECT id, nombre, email, edad
     FROM usuarios
     WHERE id = :id';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'id' => $id
]);

$usuario = $stmt->fetch();

if (!$usuario) {
    exit('Usuario no encontrado');
}

Precargar el formulario

<form method="post">

    <input
        type="text"
        name="nombre"
        value="<?php echo htmlspecialchars(
            $usuario['nombre'],
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <input
        type="email"
        name="email"
        value="<?php echo htmlspecialchars(
            $usuario['email'],
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <input
        type="number"
        name="edad"
        value="<?php echo htmlspecialchars(
            $usuario['edad'],
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <button type="submit">
        Guardar cambios
    </button>

</form>

Procesar la edición

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $nombre = trim($_POST['nombre'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $edad = trim($_POST['edad'] ?? '');

    if ($nombre === '') {
        $errores[] = 'El nombre es obligatorio';
    }

    if (!filter_var(
        $email,
        FILTER_VALIDATE_EMAIL
    )) {
        $errores[] = 'El email no es válido';
    }

    if (empty($errores)) {

        $sql =
            'UPDATE usuarios
             SET nombre = :nombre,
                 email = :email,
                 edad = :edad
             WHERE id = :id';

        $stmt = $pdo->prepare($sql);

        $stmt->execute([
            'nombre' => $nombre,
            'email' => $email,
            'edad' => $edad,
            'id' => $id
        ]);

        echo 'Usuario actualizado';
    }
}

Eliminar un registro

La operación DELETE también debe incluir una condición:

DELETE FROM usuarios
WHERE id = 5;

Desde PHP:

$sql =
    'DELETE FROM usuarios
     WHERE id = :id';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'id' => $id
]);

No eliminar mediante enlaces GET

No conviene hacer algo como:

<a href="eliminar.php?id=5">
    Eliminar
</a>

Un enlace representa naturalmente una navegación. Para una acción destructiva es preferible utilizar un formulario con POST.

Formulario para eliminar

<form
    method="post"
    action="eliminar.php"
>

    <input
        type="hidden"
        name="id"
        value="5"
    >

    <button type="submit">
        Eliminar
    </button>

</form>

Procesar la eliminación

<?php

require 'conexion.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit('Método no permitido');
}

$id = filter_input(
    INPUT_POST,
    'id',
    FILTER_VALIDATE_INT
);

if ($id === false || $id === null) {
    exit('ID no válido');
}

$sql =
    'DELETE FROM usuarios
     WHERE id = :id';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'id' => $id
]);

echo 'Usuario eliminado';

Validar que el registro exista

Antes de modificar o eliminar, podemos comprobar que el registro exista.

$stmt = $pdo->prepare(
    'SELECT id
     FROM usuarios
     WHERE id = :id'
);

$stmt->execute([
    'id' => $id
]);

if (!$stmt->fetch()) {
    exit('Usuario no encontrado');
}

Estructura sencilla de archivos

usuarios/
│
├── conexion.php
├── index.php
├── crear.php
├── editar.php
└── eliminar.php

Por ahora es una estructura simple y didáctica. Más adelante veremos cómo organizar aplicaciones más grandes.

index.php

Puede mostrar el listado de usuarios y los botones de acción:

<?php foreach ($usuarios as $usuario): ?>

    <p>

        <?php echo htmlspecialchars(
            $usuario['nombre'],
            ENT_QUOTES,
            'UTF-8'
        ); ?>

        <a href="editar.php?id=<?php
            echo $usuario['id'];
        ?>">
            Editar
        </a>

        <form
            method="post"
            action="eliminar.php"
            style="display:inline"
        >
            <input
                type="hidden"
                name="id"
                value="<?php
                    echo $usuario['id'];
                ?>"
            >

            <button type="submit">
                Eliminar
            </button>
        </form>

    </p>

<?php endforeach; ?>

Mensajes después de una operación

En una aplicación real suele ser más cómodo redirigir después de insertar, actualizar o eliminar.

header(
    'Location: index.php?mensaje=creado'
);

exit;

Luego podemos mostrar un mensaje:

$mensaje = $_GET['mensaje'] ?? '';

if ($mensaje === 'creado') {
    echo 'Usuario creado correctamente';
}

Por qué redirigir después de POST

Después de procesar un formulario, una redirección evita que el navegador intente repetir la operación si el usuario actualiza la página.

Este patrón se conoce habitualmente como:

POST
 ↓
procesamiento
 ↓
redirect
 ↓
GET

También se lo conoce como Post/Redirect/Get.

Una mejora importante: confirmar eliminaciones

Podemos pedir confirmación en el navegador:

<form
    method="post"
    action="eliminar.php"
    onsubmit="return confirm(
        '¿Seguro que querés eliminar este usuario?'
    );"
>

Esto mejora la experiencia, aunque la autorización real de la operación siempre debe controlarse también en el servidor.

Consultas preparadas en todas las operaciones

En las tres operaciones mantenemos el mismo patrón:

$sql = '... :valor ...';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'valor' => $dato
]);

Esto evita construir SQL concatenando datos externos.

Errores comunes

  • concatenar valores del usuario dentro de SQL;
  • olvidar el WHERE en un UPDATE;
  • olvidar el WHERE en un DELETE;
  • usar GET para ejecutar una eliminación;
  • no validar IDs;
  • no comprobar si el registro existe;
  • no validar el formulario antes de guardar;
  • no utilizar consultas preparadas;
  • actualizar o eliminar registros sin verificar permisos;
  • repetir un POST accidentalmente al recargar la página.

Buenas prácticas

  • validar siempre los datos antes de escribir en la base;
  • utilizar prepare() y execute();
  • validar IDs con filtros apropiados;
  • comprobar la existencia del registro;
  • usar POST para acciones que modifican datos;
  • redireccionar después de operaciones exitosas;
  • escapar datos al mostrarlos en HTML;
  • controlar permisos antes de modificar o eliminar;
  • revisar cuidadosamente las condiciones de UPDATE y DELETE.

Ejercicios

  1. crear un formulario para insertar usuarios;
  2. validar nombre, email y edad;
  3. guardar el usuario mediante PDO;
  4. mostrar el ID generado con lastInsertId();
  5. crear una página de edición;
  6. precargar el formulario con los datos actuales;
  7. actualizar mediante una consulta preparada;
  8. crear un formulario POST para eliminar;
  9. validar el ID recibido;
  10. mostrar mensajes después de cada operación;
  11. aplicar Post/Redirect/Get;
  12. agregar confirmación antes de eliminar.

Qué aprendimos

  • cómo insertar registros con PDO;
  • cómo recuperar el ID insertado;
  • cómo modificar registros con UPDATE;
  • cómo precargar un formulario;
  • cómo eliminar registros;
  • por qué debemos validar IDs;
  • por qué las operaciones destructivas no deberían ejecutarse por GET;
  • cómo combinar formularios, PDO y consultas preparadas;
  • qué es Post/Redirect/Get.

Continuá aprendiendo SQL / MySQL

Para profundizar en INSERT, UPDATE, DELETE, restricciones, relaciones y demás conceptos propios de la base de datos, podés seguir nuestra ruta completa:

👉 Ver la ruta SQL / MySQL

¿Qué sigue?

En la próxima guía vamos a integrar todo lo aprendido en un CRUD completo con PHP, MySQL y PDO: listado, alta, edición, eliminación y búsqueda dentro de una pequeña aplicación funcional.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

PHP y MySQL con PDO: conexión, consultas preparadas y lectura de datos

Hasta ahora nuestros ejemplos de PHP trabajaron con datos que existían solamente mientras se ejecutaba el script. En una aplicación real normalmente necesitamos guardar información de manera permanente.

Para eso vamos a conectar PHP con MySQL utilizando PDO. Esta guía se concentra en la conexión, las consultas de lectura y las consultas preparadas. En la siguiente veremos altas, modificaciones y eliminaciones.

¿Todavía no manejás SQL?

En esta guía utilizaremos instrucciones como CREATE TABLE, SELECT, WHERE y ORDER BY, pero no vamos a repetir toda su teoría. Para aprenderlas desde cero podés seguir nuestra ruta completa:

👉 Ruta de aprendizaje SQL / MySQL

¿Qué papel cumple MySQL?

PHP ejecuta la lógica de nuestra aplicación. MySQL se encarga de almacenar y consultar los datos.

Navegador
    ↓
PHP
    ↓
PDO
    ↓
MySQL
    ↓
PDO
    ↓
PHP
    ↓
HTML
    ↓
Navegador

PDO funciona como la capa que utilizaremos desde PHP para comunicarnos con la base de datos.

¿Qué es PDO?

PDO significa PHP Data Objects. Es una extensión de PHP que ofrece una interfaz consistente para trabajar con bases de datos.

Entre otras cosas nos permite:

  • conectarnos a MySQL;
  • ejecutar consultas;
  • obtener resultados;
  • utilizar consultas preparadas;
  • manejar errores mediante excepciones;
  • trabajar de una forma más segura con datos recibidos del usuario.

La tabla que utilizaremos

Para los ejemplos vamos a trabajar con una base llamada club_programador y una tabla usuarios.

CREATE DATABASE club_programador;

USE club_programador;

CREATE TABLE usuarios (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    edad INT
);

Recordatorio: si conceptos como clave primaria, AUTO_INCREMENT, VARCHAR o restricciones todavía no te resultan familiares, están desarrollados en la ruta SQL / MySQL.

Datos de ejemplo

INSERT INTO usuarios (nombre, email, edad)
VALUES
    ('Ana', 'ana@email.com', 25),
    ('Juan', 'juan@email.com', 31),
    ('Lucía', 'lucia@email.com', 22);

Conectarse a MySQL con PDO

Creamos un archivo conexion.php:

<?php

$host = 'localhost';
$base = 'club_programador';
$usuario = 'root';
$password = '';

$dsn = "mysql:host=$host;dbname=$base;charset=utf8mb4";

$pdo = new PDO(
    $dsn,
    $usuario,
    $password
);

¿Qué es el DSN?

El DSN describe cómo debe conectarse PDO.

mysql:host=localhost;dbname=club_programador;charset=utf8mb4

Sus partes principales son:

  • mysql: controlador que utilizaremos;
  • host: servidor donde está MySQL;
  • dbname: nombre de la base;
  • charset: codificación utilizada en la conexión.

Usar utf8mb4

Para aplicaciones nuevas conviene trabajar con utf8mb4, ya que permite almacenar correctamente Unicode completo.

Manejar errores con excepciones

La conexión puede fallar por muchos motivos: servidor apagado, contraseña incorrecta, base inexistente, etc.

Podemos pedirle a PDO que lance excepciones:

$pdo->setAttribute(
    PDO::ATTR_ERRMODE,
    PDO::ERRMODE_EXCEPTION
);

Y manejar la conexión mediante try/catch:

<?php

$host = 'localhost';
$base = 'club_programador';
$usuario = 'root';
$password = '';

$dsn =
    "mysql:host=$host;dbname=$base;charset=utf8mb4";

try {

    $pdo = new PDO(
        $dsn,
        $usuario,
        $password
    );

    $pdo->setAttribute(
        PDO::ATTR_ERRMODE,
        PDO::ERRMODE_EXCEPTION
    );

    echo 'Conexión correcta';

} catch (PDOException $e) {

    echo 'No se pudo conectar a la base de datos';

}

En producción no conviene mostrar al usuario detalles internos de la excepción.

Configurar el modo de resultados

También podemos configurar PDO para que devuelva filas como arrays asociativos:

$pdo->setAttribute(
    PDO::ATTR_DEFAULT_FETCH_MODE,
    PDO::FETCH_ASSOC
);

Así una fila puede verse como:

[
    'id' => 1,
    'nombre' => 'Ana',
    'email' => 'ana@email.com',
    'edad' => 25
]

Archivo conexion.php recomendado

<?php

$host = 'localhost';
$base = 'club_programador';
$usuario = 'root';
$password = '';

$dsn =
    "mysql:host=$host;dbname=$base;charset=utf8mb4";

try {

    $pdo = new PDO(
        $dsn,
        $usuario,
        $password,
        [
            PDO::ATTR_ERRMODE =>
                PDO::ERRMODE_EXCEPTION,

            PDO::ATTR_DEFAULT_FETCH_MODE =>
                PDO::FETCH_ASSOC
        ]
    );

} catch (PDOException $e) {

    exit('No se pudo conectar a la base de datos');
}

Primer SELECT desde PHP

Una vez conectados podemos ejecutar una consulta:

$sql = 'SELECT id, nombre, email, edad
        FROM usuarios';

$stmt = $pdo->query($sql);

query() ejecuta la consulta y devuelve un objeto que representa el resultado.

La sentencia SELECT está explicada en detalle, junto con filtros, ordenamiento y agregaciones, dentro de la ruta de SQL / MySQL.

Obtener una fila con fetch()

$usuario = $stmt->fetch();

echo $usuario['nombre'];

fetch() obtiene una fila del resultado.

Obtener todas las filas con fetchAll()

$usuarios = $stmt->fetchAll();

Ahora $usuarios es un array que contiene todas las filas.

Recorrer los resultados

foreach ($usuarios as $usuario) {

    echo $usuario['nombre'];
    echo '<br>';
}

Mostrar resultados dentro de HTML

<?php

require 'conexion.php';

$sql =
    'SELECT id, nombre, email, edad
     FROM usuarios
     ORDER BY nombre';

$stmt = $pdo->query($sql);

$usuarios = $stmt->fetchAll();

?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Usuarios</title>
</head>
<body>

<h1>Usuarios</h1>

<ul>

    <?php foreach ($usuarios as $usuario): ?>

        <li>
            <?php echo htmlspecialchars(
                $usuario['nombre'],
                ENT_QUOTES,
                'UTF-8'
            ); ?>

            -

            <?php echo htmlspecialchars(
                $usuario['email'],
                ENT_QUOTES,
                'UTF-8'
            ); ?>
        </li>

    <?php endforeach; ?>

</ul>

</body>
</html>

Separar conexión y consulta

Una estructura sencilla puede quedar así:

mi-proyecto/
│
├── conexion.php
├── usuarios.php
└── index.php

La conexión queda centralizada y los demás archivos pueden reutilizarla mediante:

require 'conexion.php';

¿Cuándo usar query()?

query() resulta práctico cuando la consulta es completamente fija y no contiene datos proporcionados por el usuario.

$stmt = $pdo->query(
    'SELECT id, nombre
     FROM usuarios
     ORDER BY nombre'
);

El problema aparece cuando necesitamos incorporar valores dinámicos.

El error que debemos evitar

Supongamos que recibimos un ID desde la URL.

No debemos construir la consulta concatenando directamente ese valor:

$id = $_GET['id'];

$sql =
    "SELECT *
     FROM usuarios
     WHERE id = " . $id;

Además de producir código frágil, concatenar datos externos dentro de SQL puede abrir la puerta a inyección SQL.

¿Qué es una inyección SQL?

Ocurre cuando un dato controlado por un usuario modifica la estructura de una consulta SQL de una forma que la aplicación no esperaba.

La solución habitual es utilizar consultas preparadas.

Consultas preparadas con prepare()

$sql =
    'SELECT id, nombre, email, edad
     FROM usuarios
     WHERE id = :id';

$stmt = $pdo->prepare($sql);

:id es un placeholder: un lugar reservado para un valor.

Ejecutar la consulta preparada

$stmt->execute([
    'id' => 1
]);

$usuario = $stmt->fetch();

PDO se encarga de enviar la consulta y el valor de forma apropiada.

Ejemplo completo: buscar por ID

<?php

require 'conexion.php';

$id = $_GET['id'] ?? null;

if ($id === null) {
    exit('Falta el ID');
}

$sql =
    'SELECT id, nombre, email, edad
     FROM usuarios
     WHERE id = :id';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'id' => $id
]);

$usuario = $stmt->fetch();

if (!$usuario) {
    exit('Usuario no encontrado');
}

?>

<h1>
    <?php echo htmlspecialchars(
        $usuario['nombre'],
        ENT_QUOTES,
        'UTF-8'
    ); ?>
</h1>

<p>
    Email:
    <?php echo htmlspecialchars(
        $usuario['email'],
        ENT_QUOTES,
        'UTF-8'
    ); ?>
</p>

Filtrar por email

$sql =
    'SELECT id, nombre, email
     FROM usuarios
     WHERE email = :email';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'email' => $email
]);

$usuario = $stmt->fetch();

Buscar por nombre con LIKE

$sql =
    'SELECT id, nombre, email
     FROM usuarios
     WHERE nombre LIKE :nombre
     ORDER BY nombre';

$stmt = $pdo->prepare($sql);

$stmt->execute([
    'nombre' => '%' . $busqueda . '%'
]);

$usuarios = $stmt->fetchAll();

Si querés entender mejor operadores como LIKE, filtros con WHERE, ordenamiento con ORDER BY y demás herramientas SQL, consultá la ruta SQL / MySQL. En este curso de PHP nos concentramos en cómo ejecutar esas consultas desde PHP.

query() frente a prepare()

Una regla sencilla para comenzar:

  • si la consulta es fija y no incorpora datos externos, query() puede ser suficiente;
  • si la consulta contiene valores variables, especialmente datos del usuario, preferí prepare() y execute().

PDO no reemplaza la validación

Las consultas preparadas ayudan a separar datos y SQL, pero todavía debemos validar los datos según las reglas de nuestra aplicación.

Por ejemplo, si esperamos un ID entero:

$id = filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT
);

if ($id === false || $id === null) {
    exit('ID no válido');
}

Flujo completo de una consulta

1. El navegador solicita usuarios.php
2. PHP carga conexion.php
3. PDO se conecta a MySQL
4. PHP prepara o ejecuta un SELECT
5. MySQL procesa la consulta
6. MySQL devuelve las filas
7. PDO convierte los resultados
8. PHP trabaja con arrays
9. PHP genera HTML
10. El navegador recibe el HTML

Ejemplo práctico: listado con buscador

<?php

require 'conexion.php';

$busqueda = trim($_GET['q'] ?? '');

if ($busqueda === '') {

    $stmt = $pdo->query(
        'SELECT id, nombre, email
         FROM usuarios
         ORDER BY nombre'
    );

} else {

    $stmt = $pdo->prepare(
        'SELECT id, nombre, email
         FROM usuarios
         WHERE nombre LIKE :busqueda
         ORDER BY nombre'
    );

    $stmt->execute([
        'busqueda' => '%' . $busqueda . '%'
    ]);
}

$usuarios = $stmt->fetchAll();

?>

<form method="get">

    <input
        type="text"
        name="q"
        value="<?php echo htmlspecialchars(
            $busqueda,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <button type="submit">
        Buscar
    </button>

</form>

<ul>

<?php foreach ($usuarios as $usuario): ?>

    <li>
        <?php echo htmlspecialchars(
            $usuario['nombre'],
            ENT_QUOTES,
            'UTF-8'
        ); ?>

        -

        <?php echo htmlspecialchars(
            $usuario['email'],
            ENT_QUOTES,
            'UTF-8'
        ); ?>
    </li>

<?php endforeach; ?>

</ul>

Qué estamos reutilizando de guías anteriores

Este ejemplo combina muchos conceptos que ya aprendimos:

  • formularios GET;
  • $_GET;
  • arrays;
  • condicionales;
  • foreach;
  • funciones incorporadas;
  • htmlspecialchars();
  • validación;
  • HTML y PHP en un mismo archivo.

Ahora agregamos una nueva pieza: persistencia mediante MySQL.

Errores comunes

  • escribir mal el host, base, usuario o contraseña;
  • olvidar especificar utf8mb4;
  • mostrar detalles internos de excepciones a usuarios finales;
  • concatenar valores recibidos del usuario dentro de SQL;
  • confundir fetch() con fetchAll();
  • olvidar ejecutar una consulta preparada;
  • suponer que una consulta siempre devuelve filas;
  • mostrar resultados en HTML sin escaparlos;
  • creer que PDO reemplaza la validación de los datos.

Buenas prácticas

  • centralizar la conexión;
  • utilizar utf8mb4;
  • trabajar con excepciones;
  • preferir arrays asociativos para resultados;
  • usar consultas preparadas cuando intervienen valores variables;
  • validar entradas antes de utilizarlas;
  • escapar datos cuando se imprimen en HTML;
  • mantener separada la teoría SQL de la lógica PHP cuando sea posible.

Ejercicios

  1. crear la base club_programador;
  2. crear la tabla usuarios;
  3. insertar cinco usuarios de prueba;
  4. crear conexion.php;
  5. listar todos los usuarios;
  6. mostrar solamente nombre y email;
  7. ordenar por nombre;
  8. buscar un usuario por ID mediante una consulta preparada;
  9. buscar por email;
  10. crear un buscador por nombre con LIKE;
  11. mostrar un mensaje cuando no existen resultados;
  12. crear una página de detalle usuario.php?id=1.

Qué aprendimos

  • cómo se relacionan PHP y MySQL;
  • qué es PDO;
  • qué es un DSN;
  • cómo conectarnos a MySQL;
  • cómo manejar errores;
  • cómo ejecutar un SELECT;
  • cómo usar fetch() y fetchAll();
  • cómo recorrer resultados;
  • qué son las consultas preparadas;
  • qué son los placeholders;
  • por qué debemos evitar concatenar datos del usuario dentro de SQL;
  • cómo integrar consultas, arrays y HTML.

Continuá aprendiendo SQL / MySQL

En las próximas guías de PHP utilizaremos cada vez más SQL. Siempre que necesites profundizar en una sentencia o concepto de base de datos, podés volver a nuestra ruta específica:

👉 Ver la ruta completa de SQL / MySQL

¿Qué sigue?

En la próxima guía veremos INSERT, UPDATE y DELETE con PHP y PDO: cómo guardar formularios en MySQL, modificar registros existentes y eliminar datos utilizando consultas preparadas.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

Formularios avanzados en PHP: select, radio, checkbox y subida de archivos

En la guía anterior vimos cómo enviar un formulario HTML a PHP utilizando GET y POST, cómo validar datos y cómo volver a mostrar los valores cuando existe un error.

Ahora vamos a trabajar con formularios más completos: select, radio buttons, checkboxes, múltiples valores y carga de archivos. Son controles que aparecen constantemente en registros, perfiles, sistemas administrativos y aplicaciones reales.

Formulario avanzado: qué vamos a construir

Vamos a crear un formulario de perfil con:

  • nombre y email;
  • provincia mediante select;
  • tipo de usuario mediante radio buttons;
  • intereses mediante checkboxes;
  • biografía con textarea;
  • foto de perfil mediante carga de archivo;
  • validación del lado del servidor.

El formulario HTML completo

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Perfil</title>
</head>
<body>

    <h1>Completar perfil</h1>

    <form method="post" enctype="multipart/form-data">

        <label for="nombre">Nombre:</label>
        <input type="text" id="nombre" name="nombre">

        <br><br>

        <label for="email">Email:</label>
        <input type="email" id="email" name="email">

        <br><br>

        <label for="provincia">Provincia:</label>
        <select id="provincia" name="provincia">
            <option value="">Seleccionar...</option>
            <option value="la-pampa">La Pampa</option>
            <option value="cordoba">Córdoba</option>
            <option value="mendoza">Mendoza</option>
        </select>

        <br><br>

        <p>Tipo de usuario:</p>

        <label>
            <input type="radio" name="tipo" value="estudiante">
            Estudiante
        </label>

        <label>
            <input type="radio" name="tipo" value="profesional">
            Profesional
        </label>

        <br><br>

        <p>Intereses:</p>

        <label>
            <input type="checkbox" name="intereses[]" value="php">
            PHP
        </label>

        <label>
            <input type="checkbox" name="intereses[]" value="javascript">
            JavaScript
        </label>

        <label>
            <input type="checkbox" name="intereses[]" value="mysql">
            MySQL
        </label>

        <br><br>

        <label for="bio">Biografía:</label>
        <textarea id="bio" name="bio" rows="5"></textarea>

        <br><br>

        <label for="foto">Foto de perfil:</label>
        <input type="file" id="foto" name="foto">

        <br><br>

        <button type="submit">Guardar perfil</button>

    </form>

</body>
</html>

Select

El elemento select permite elegir una opción de una lista.

<select name="provincia">
    <option value="">Seleccionar...</option>
    <option value="la-pampa">La Pampa</option>
    <option value="cordoba">Córdoba</option>
</select>

Lo que recibe PHP es el atributo value de la opción seleccionada.

$provincia = $_POST['provincia'] ?? '';

Validar un select

No alcanza con comprobar que el valor no esté vacío. También conviene verificar que pertenezca al conjunto permitido.

$provinciasPermitidas = [
    'la-pampa',
    'cordoba',
    'mendoza'
];

if (!in_array($provincia, $provinciasPermitidas, true)) {
    $errores[] = 'La provincia seleccionada no es válida';
}

Esto evita confiar ciegamente en lo enviado por el navegador.

Radio buttons

Los radio buttons permiten elegir una sola opción dentro de un grupo.

<label>
    <input type="radio" name="tipo" value="estudiante">
    Estudiante
</label>

<label>
    <input type="radio" name="tipo" value="profesional">
    Profesional
</label>

Ambos comparten el mismo name. Por eso solamente uno puede quedar seleccionado.

$tipo = $_POST['tipo'] ?? '';

Checkboxes

Los checkboxes permiten seleccionar varias opciones.

Para recibir múltiples valores en PHP utilizamos corchetes en el atributo name:

<input type="checkbox" name="intereses[]" value="php">
<input type="checkbox" name="intereses[]" value="javascript">
<input type="checkbox" name="intereses[]" value="mysql">

Si el usuario selecciona PHP y MySQL, PHP puede recibir:

$_POST['intereses'] = [
    'php',
    'mysql'
];

Leer varios checkboxes

$intereses = $_POST['intereses'] ?? [];

foreach ($intereses as $interes) {
    echo htmlspecialchars($interes, ENT_QUOTES, 'UTF-8');
}

Validar múltiples valores

Conviene comprobar cada elemento recibido.

$interesesPermitidos = [
    'php',
    'javascript',
    'mysql'
];

foreach ($intereses as $interes) {

    if (!in_array($interes, $interesesPermitidos, true)) {
        $errores[] = 'Se recibió un interés no válido';
        break;
    }
}

Mantener un option seleccionado

Cuando el formulario tiene errores, podemos conservar la opción elegida:

<option
    value="la-pampa"
    <?php echo $provincia === 'la-pampa' ? 'selected' : ''; ?>
>
    La Pampa
</option>

Mantener un radio seleccionado

<input
    type="radio"
    name="tipo"
    value="estudiante"
    <?php echo $tipo === 'estudiante' ? 'checked' : ''; ?>
>

Mantener checkboxes seleccionados

<input
    type="checkbox"
    name="intereses[]"
    value="php"
    <?php echo in_array('php', $intereses, true) ? 'checked' : ''; ?>
>

Textarea

En un textarea el valor no se coloca en un atributo value. Se escribe entre la etiqueta de apertura y la de cierre.

<textarea name="bio"><?php
echo htmlspecialchars(
    $bio,
    ENT_QUOTES,
    'UTF-8'
);
?></textarea>

Carga de archivos

Para subir archivos necesitamos agregar este atributo al formulario:

<form method="post" enctype="multipart/form-data">

Sin multipart/form-data, el archivo no se enviará correctamente.

El input file

<input type="file" name="foto">

Los archivos no llegan mediante $_POST. PHP utiliza otra superglobal:

$_FILES

Qué contiene $_FILES

Para un campo llamado foto podemos tener:

$_FILES['foto'] = [
    'name' => 'perfil.jpg',
    'type' => 'image/jpeg',
    'tmp_name' => '/tmp/php123.tmp',
    'error' => 0,
    'size' => 153245
];

Los datos más importantes son:

  • name: nombre original;
  • tmp_name: ubicación temporal;
  • error: código de error;
  • size: tamaño en bytes.

Comprobar si la carga fue correcta

if (
    isset($_FILES['foto']) &&
    $_FILES['foto']['error'] === UPLOAD_ERR_OK
) {
    echo 'El archivo llegó correctamente';
}

Validar el tamaño

Por ejemplo, podemos establecer un máximo de 2 MB:

$maximo = 2 * 1024 * 1024;

if ($_FILES['foto']['size'] > $maximo) {
    $errores[] = 'La imagen no puede superar los 2 MB';
}

No confiar solamente en la extensión

Un nombre como foto.jpg no garantiza que el archivo realmente sea una imagen.

La validación de archivos requiere especial cuidado. Para una introducción simple podemos comenzar comprobando extensión y tamaño, pero en una aplicación real también conviene verificar el tipo real del archivo.

Obtener la extensión

$extension = strtolower(
    pathinfo(
        $_FILES['foto']['name'],
        PATHINFO_EXTENSION
    )
);

Validar extensiones permitidas

$permitidas = ['jpg', 'jpeg', 'png'];

if (!in_array($extension, $permitidas, true)) {
    $errores[] = 'Formato de imagen no permitido';
}

Mover el archivo subido

Cuando PHP recibe un archivo, primero lo guarda temporalmente.

Si todo es correcto, podemos moverlo:

$destino = __DIR__ . '/uploads/perfil.' . $extension;

if (!move_uploaded_file(
    $_FILES['foto']['tmp_name'],
    $destino
)) {
    $errores[] = 'No se pudo guardar la imagen';
}

move_uploaded_file() está diseñada específicamente para archivos recibidos mediante HTTP POST.

Evitar usar directamente el nombre original

No conviene guardar archivos utilizando directamente:

$_FILES['foto']['name']

El nombre puede contener datos inesperados o coincidir con otro archivo existente.

En proyectos reales suele generarse un nombre controlado por la aplicación.

Formulario completo con procesamiento

Ahora vamos a unir todos los conceptos en un archivo llamado perfil.php.

<?php

$nombre = '';
$email = '';
$provincia = '';
$tipo = '';
$intereses = [];
$bio = '';

$errores = [];
$guardado = false;

$provinciasPermitidas = [
    'la-pampa',
    'cordoba',
    'mendoza'
];

$tiposPermitidos = [
    'estudiante',
    'profesional'
];

$interesesPermitidos = [
    'php',
    'javascript',
    'mysql'
];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $nombre = trim($_POST['nombre'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $provincia = $_POST['provincia'] ?? '';
    $tipo = $_POST['tipo'] ?? '';
    $intereses = $_POST['intereses'] ?? [];
    $bio = trim($_POST['bio'] ?? '');

    if ($nombre === '') {
        $errores[] = 'El nombre es obligatorio';
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errores[] = 'El email no es válido';
    }

    if (!in_array(
        $provincia,
        $provinciasPermitidas,
        true
    )) {
        $errores[] = 'Provincia no válida';
    }

    if (!in_array(
        $tipo,
        $tiposPermitidos,
        true
    )) {
        $errores[] = 'Tipo de usuario no válido';
    }

    if (!is_array($intereses)) {
        $intereses = [];
        $errores[] = 'Los intereses no son válidos';
    }

    foreach ($intereses as $interes) {

        if (!in_array(
            $interes,
            $interesesPermitidos,
            true
        )) {
            $errores[] = 'Interés no válido';
            break;
        }
    }

    if (
        isset($_FILES['foto']) &&
        $_FILES['foto']['error'] === UPLOAD_ERR_OK
    ) {

        $maximo = 2 * 1024 * 1024;

        if ($_FILES['foto']['size'] > $maximo) {
            $errores[] =
                'La imagen no puede superar los 2 MB';
        }

        $extension = strtolower(
            pathinfo(
                $_FILES['foto']['name'],
                PATHINFO_EXTENSION
            )
        );

        $extensionesPermitidas = [
            'jpg',
            'jpeg',
            'png'
        ];

        if (!in_array(
            $extension,
            $extensionesPermitidas,
            true
        )) {
            $errores[] =
                'Formato de imagen no permitido';
        }
    }

    if (empty($errores)) {

        if (
            isset($_FILES['foto']) &&
            $_FILES['foto']['error'] === UPLOAD_ERR_OK
        ) {

            $nombreArchivo =
                'perfil_' . time() . '.' . $extension;

            $destino =
                __DIR__ . '/uploads/' . $nombreArchivo;

            if (!move_uploaded_file(
                $_FILES['foto']['tmp_name'],
                $destino
            )) {
                $errores[] =
                    'No se pudo guardar la imagen';
            }
        }
    }

    if (empty($errores)) {
        $guardado = true;
    }
}

?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Perfil</title>
</head>
<body>

<h1>Completar perfil</h1>

<?php if ($guardado): ?>

    <p>Perfil guardado correctamente.</p>

<?php else: ?>

    <?php if (!empty($errores)): ?>

        <ul>
            <?php foreach ($errores as $error): ?>
                <li>
                    <?php echo htmlspecialchars(
                        $error,
                        ENT_QUOTES,
                        'UTF-8'
                    ); ?>
                </li>
            <?php endforeach; ?>
        </ul>

    <?php endif; ?>

    <form
        method="post"
        enctype="multipart/form-data"
    >

        <label for="nombre">Nombre:</label>

        <input
            type="text"
            id="nombre"
            name="nombre"
            value="<?php echo htmlspecialchars(
                $nombre,
                ENT_QUOTES,
                'UTF-8'
            ); ?>"
        >

        <br><br>

        <label for="email">Email:</label>

        <input
            type="email"
            id="email"
            name="email"
            value="<?php echo htmlspecialchars(
                $email,
                ENT_QUOTES,
                'UTF-8'
            ); ?>"
        >

        <br><br>

        <label for="provincia">
            Provincia:
        </label>

        <select
            id="provincia"
            name="provincia"
        >
            <option value="">
                Seleccionar...
            </option>

            <option
                value="la-pampa"
                <?php echo
                    $provincia === 'la-pampa'
                    ? 'selected'
                    : '';
                ?>
            >
                La Pampa
            </option>

            <option
                value="cordoba"
                <?php echo
                    $provincia === 'cordoba'
                    ? 'selected'
                    : '';
                ?>
            >
                Córdoba
            </option>

            <option
                value="mendoza"
                <?php echo
                    $provincia === 'mendoza'
                    ? 'selected'
                    : '';
                ?>
            >
                Mendoza
            </option>
        </select>

        <br><br>

        <p>Tipo de usuario:</p>

        <label>
            <input
                type="radio"
                name="tipo"
                value="estudiante"
                <?php echo
                    $tipo === 'estudiante'
                    ? 'checked'
                    : '';
                ?>
            >
            Estudiante
        </label>

        <label>
            <input
                type="radio"
                name="tipo"
                value="profesional"
                <?php echo
                    $tipo === 'profesional'
                    ? 'checked'
                    : '';
                ?>
            >
            Profesional
        </label>

        <br><br>

        <p>Intereses:</p>

        <label>
            <input
                type="checkbox"
                name="intereses[]"
                value="php"
                <?php echo in_array(
                    'php',
                    $intereses,
                    true
                ) ? 'checked' : ''; ?>
            >
            PHP
        </label>

        <label>
            <input
                type="checkbox"
                name="intereses[]"
                value="javascript"
                <?php echo in_array(
                    'javascript',
                    $intereses,
                    true
                ) ? 'checked' : ''; ?>
            >
            JavaScript
        </label>

        <label>
            <input
                type="checkbox"
                name="intereses[]"
                value="mysql"
                <?php echo in_array(
                    'mysql',
                    $intereses,
                    true
                ) ? 'checked' : ''; ?>
            >
            MySQL
        </label>

        <br><br>

        <label for="bio">Biografía:</label>

        <textarea
            id="bio"
            name="bio"
            rows="5"
        ><?php echo htmlspecialchars(
            $bio,
            ENT_QUOTES,
            'UTF-8'
        ); ?></textarea>

        <br><br>

        <label for="foto">
            Foto de perfil:
        </label>

        <input
            type="file"
            id="foto"
            name="foto"
            accept=".jpg,.jpeg,.png"
        >

        <br><br>

        <button type="submit">
            Guardar perfil
        </button>

    </form>

<?php endif; ?>

</body>
</html>

Qué ocurre en este ejemplo

La secuencia es:

1. El usuario abre perfil.php
2. PHP muestra el formulario
3. El usuario completa los campos
4. Selecciona opciones
5. Adjunta una imagen
6. Presiona Guardar perfil
7. El navegador envía POST multipart/form-data
8. Los campos llegan a $_POST
9. El archivo llega a $_FILES
10. PHP valida todos los datos
11. Si hay errores, vuelve a mostrar el formulario
12. Si todo está correcto, mueve el archivo
13. La aplicación continúa con el procesamiento

Campos opcionales

No todos los campos tienen que ser obligatorios.

Por ejemplo, una biografía puede ser opcional:

$bio = trim($_POST['bio'] ?? '');

if ($bio !== '' && strlen($bio) > 500) {
    $errores[] =
        'La biografía no puede superar 500 caracteres';
}

Primero comprobamos si existe contenido y solamente entonces aplicamos la regla adicional.

HTML no es una barrera de seguridad

Podemos escribir:

<input
    type="file"
    name="foto"
    accept=".jpg,.jpeg,.png"
>

pero accept solamente ayuda al usuario a elegir archivos adecuados. La validación real debe realizarse nuevamente en PHP.

Errores comunes

  • olvidar enctype="multipart/form-data";
  • intentar leer archivos desde $_POST;
  • olvidar los corchetes en checkboxes múltiples;
  • confiar en valores de select, radio o checkbox sin validarlos;
  • confiar solamente en la extensión del archivo;
  • guardar directamente el nombre original;
  • no limitar el tamaño de los archivos;
  • no conservar las opciones seleccionadas cuando existe un error;
  • confiar solamente en atributos HTML como required o accept.

Buenas prácticas

  • validar todos los datos nuevamente en PHP;
  • usar listas de valores permitidos;
  • tratar los archivos con reglas más estrictas que un campo de texto;
  • generar nombres controlados para los archivos;
  • limitar tamaño y formatos;
  • mostrar mensajes claros al usuario;
  • mantener los datos cargados después de un error;
  • escapar siempre la salida cuando se imprime dentro de HTML.

Ejercicios

  1. crear un select con cinco provincias;
  2. crear un grupo de radio buttons para elegir nivel de experiencia;
  3. crear checkboxes para seleccionar tres tecnologías;
  4. mostrar en PHP todas las tecnologías elegidas;
  5. mantener seleccionados los campos después de un error;
  6. agregar una biografía opcional con máximo de caracteres;
  7. crear un input de archivo para una imagen;
  8. limitar el archivo a 2 MB;
  9. aceptar solamente JPG y PNG;
  10. guardar el archivo dentro de una carpeta uploads;
  11. crear un formulario de perfil completo con todos los controles vistos;
  12. agregar nuevas reglas de validación al ejemplo final.

Qué aprendimos

  • cómo utilizar select;
  • cómo trabajar con radio buttons;
  • cómo enviar múltiples checkboxes;
  • cómo recibir arrays desde un formulario;
  • cómo conservar selecciones después de un error;
  • cómo procesar un textarea;
  • cómo funciona multipart/form-data;
  • cómo utilizar $_FILES;
  • cómo validar tamaño y extensión;
  • cómo mover archivos subidos;
  • por qué la validación del servidor sigue siendo obligatoria.

¿Qué sigue?

Con esto ya tenemos una base muy sólida de PHP. El siguiente paso será comenzar con PHP y MySQL: conexión a una base de datos mediante PDO, consultas preparadas y lectura de registros.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

Formularios HTML con PHP: GET, POST, validación y procesamiento paso a paso

En las guías anteriores trabajamos con variables, condicionales, bucles, arrays y funciones. Ahora vamos a conectar PHP con algo que cualquier usuario puede utilizar directamente: un formulario HTML.

La idea de esta guía es entender la secuencia completa, no solamente copiar código. Vamos a construir un formulario real, enviarlo al servidor, recibir los datos con PHP, validarlos y volver a mostrar el formulario cuando exista un error.

La secuencia completa

Cuando una persona completa un formulario y presiona el botón Enviar, ocurre algo parecido a esto:

Formulario HTML
      ↓
El usuario completa los campos
      ↓
Presiona "Enviar"
      ↓
El navegador crea una petición HTTP
      ↓
Los datos llegan al servidor
      ↓
PHP recibe los datos
      ↓
PHP valida la información
      ↓
Se procesa la operación
      ↓
El servidor devuelve una respuesta HTML

Este flujo es fundamental para entender el desarrollo web con PHP.

Primer formulario HTML

Empecemos con un archivo muy simple llamado formulario.html:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Mi primer formulario</title>
</head>
<body>

    <h1>Registro de usuario</h1>

    <form action="procesar.php" method="post">
        <label for="nombre">Nombre:</label>
        <input type="text" id="nombre" name="nombre">

        <br><br>

        <label for="email">Email:</label>
        <input type="email" id="email" name="email">

        <br><br>

        <button type="submit">Enviar</button>
    </form>

</body>
</html>

¿Qué hace la etiqueta form?

La etiqueta <form> indica que ese bloque contiene datos que pueden enviarse al servidor.

<form action="procesar.php" method="post">

Hay dos atributos muy importantes:

  • action: indica a qué archivo se enviarán los datos;
  • method: indica cómo se enviarán.

El atributo name es clave

Dentro de un formulario, PHP recibe los campos utilizando el atributo name.

<input type="text" name="nombre">

El atributo id es útil para asociar etiquetas, JavaScript o CSS. Pero para enviar el dato al servidor, el atributo importante es name.

Qué recibe PHP

Si el usuario escribe:

Nombre: Ana
Email: ana@email.com

el navegador enviará aproximadamente estos pares de datos:

nombre = Ana
email = ana@email.com

Como usamos method="post", PHP los coloca dentro de la variable superglobal $_POST.

procesar.php

Creamos ahora un segundo archivo:

<?php

$nombre = $_POST['nombre'];
$email = $_POST['email'];

echo "Nombre: " . $nombre;
echo "<br>";
echo "Email: " . $email;

Si completamos el formulario y presionamos Enviar, el navegador solicitará procesar.php y PHP podrá leer los valores enviados.

¿Qué es $_POST?

$_POST es un array asociativo especial proporcionado por PHP.

Si el formulario contiene:

<input type="text" name="nombre">
<input type="email" name="email">

PHP puede recibir algo similar a:

$_POST = [
    'nombre' => 'Ana',
    'email' => 'ana@email.com'
];

Por eso lo que aprendimos sobre arrays asociativos empieza a tener una aplicación directa.

Variables superglobales

PHP incluye algunas variables especiales que están disponibles en cualquier ámbito del script. Se llaman superglobales.

Algunas de las más utilizadas son:

$_GET
$_POST
$_FILES
$_SERVER
$_SESSION
$_COOKIE

En esta guía nos concentraremos principalmente en $_GET, $_POST y $_SERVER.

GET y POST

Los dos métodos más comunes para enviar formularios son GET y POST.

Formulario usando GET

<form action="buscar.php" method="get">
    <input type="text" name="q">
    <button type="submit">Buscar</button>
</form>

Si escribimos:

php

el navegador podría abrir una URL similar a:

buscar.php?q=php

¿Qué significa la URL?

La parte que aparece después del signo ? se llama query string.

?q=php

Si hubiera más de un dato:

buscar.php?categoria=php&nivel=basico

Los valores se separan con &.

Leer datos GET

<?php

$busqueda = $_GET['q'];

echo "Buscaste: " . $busqueda;

¿Cuándo conviene GET?

GET es especialmente útil cuando la información representa una consulta y queremos que pueda formar parte de la URL.

Ejemplos:

  • buscadores;
  • filtros;
  • paginación;
  • ordenamientos;
  • parámetros que queremos poder compartir mediante un enlace.

¿Cuándo conviene POST?

POST suele utilizarse cuando estamos enviando información para realizar una operación.

Ejemplos:

  • registrar un usuario;
  • crear una publicación;
  • enviar un mensaje;
  • guardar cambios;
  • procesar un formulario de contacto.

Que POST no muestre los datos en la URL no significa que los datos estén cifrados. La protección durante el transporte depende de utilizar HTTPS.

Evitar errores cuando un campo no existe

Este código puede generar una advertencia si todavía no se envió el formulario:

$nombre = $_POST['nombre'];

Una alternativa muy utilizada es el operador ??:

$nombre = $_POST['nombre'] ?? '';

Si la clave nombre no existe, se utiliza un string vacío.

Detectar si el formulario fue enviado

Podemos consultar el método HTTP utilizando $_SERVER['REQUEST_METHOD'].

<?php

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    echo "El formulario fue enviado";
}

Esto permite tener el formulario y su procesamiento dentro del mismo archivo.

Formulario y PHP en el mismo archivo

Ahora vamos a crear registro.php.

<?php

$nombre = '';
$email = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nombre = $_POST['nombre'] ?? '';
    $email = $_POST['email'] ?? '';
}

?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Registro</title>
</head>
<body>

    <h1>Registro</h1>

    <form method="post">
        <label for="nombre">Nombre:</label>
        <input
            type="text"
            id="nombre"
            name="nombre"
            value="<?php echo htmlspecialchars($nombre); ?>"
        >

        <br><br>

        <label for="email">Email:</label>
        <input
            type="email"
            id="email"
            name="email"
            value="<?php echo htmlspecialchars($email); ?>"
        >

        <br><br>

        <button type="submit">Enviar</button>
    </form>

</body>
</html>

Como no indicamos action, el formulario se envía al mismo archivo.

¿Por qué usamos htmlspecialchars()?

Supongamos que un usuario escribe caracteres especiales o incluso código HTML.

Si imprimimos ese contenido directamente dentro de la página, el navegador podría interpretarlo como HTML.

htmlspecialchars() convierte caracteres especiales en una representación segura para mostrarlos como texto.

echo htmlspecialchars($nombre);

Esto es muy importante al mostrar información que proviene del usuario.

Validar campos obligatorios

Ahora vamos a comprobar que nombre y email tengan contenido.

<?php

$nombre = $_POST['nombre'] ?? '';
$email = $_POST['email'] ?? '';

$errores = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    if (trim($nombre) === '') {
        $errores[] = 'El nombre es obligatorio';
    }

    if (trim($email) === '') {
        $errores[] = 'El email es obligatorio';
    }
}

¿Qué hace trim()?

trim() elimina espacios al comienzo y al final de un string.

De esta manera, un usuario que escriba solamente espacios no supera la validación.

Validar un email

PHP incluye filter_var(), que permite aplicar distintos filtros.

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errores[] = 'El email no es válido';
}

No necesitamos construir manualmente una expresión regular para un caso común como este.

Validar edad

Agreguemos un campo numérico:

<label for="edad">Edad:</label>
<input
    type="number"
    id="edad"
    name="edad"
    min="0"
>

Y en PHP:

$edad = $_POST['edad'] ?? '';

if ($edad === '') {
    $errores[] = 'La edad es obligatoria';
} elseif (filter_var($edad, FILTER_VALIDATE_INT) === false) {
    $errores[] = 'La edad debe ser un número entero';
} elseif ((int) $edad < 18) {
    $errores[] = 'Debés ser mayor de edad';
}

Validación del navegador y validación del servidor

HTML también permite realizar algunas validaciones:

<input type="text" name="nombre" required>
<input type="email" name="email" required>
<input type="number" name="edad" min="18" required>

Estas validaciones mejoran la experiencia del usuario, pero no reemplazan la validación en PHP.

Un usuario o programa puede enviar una petición directamente al servidor sin utilizar nuestro formulario HTML.

Por eso, las reglas importantes siempre deben comprobarse nuevamente en el servidor.

Validar no es lo mismo que sanitizar

Validar significa comprobar si un dato cumple determinadas reglas.

Por ejemplo:

¿El email tiene un formato válido?
¿La edad es un entero?
¿El nombre está completo?

Sanitizar o escapar significa preparar los datos para un contexto determinado.

Por ejemplo, al mostrar texto dentro de HTML:

htmlspecialchars($nombre, ENT_QUOTES, 'UTF-8');

Una buena aplicación suele necesitar ambas cosas.

Mostrar errores en el formulario

<?php if (!empty($errores)): ?>

    <ul>
        <?php foreach ($errores as $error): ?>
            <li>
                <?php echo htmlspecialchars($error); ?>
            </li>
        <?php endforeach; ?>
    </ul>

<?php endif; ?>

¿Qué hace empty()?

empty() permite comprobar si una variable está vacía.

if (empty($errores)) {
    echo "No hay errores";
}

Como $errores es un array, un array sin elementos se considera vacío.

Mantener los valores cuando ocurre un error

Si el usuario completa un formulario y hay un error, no queremos obligarlo a escribir todo de nuevo.

Por eso usamos el valor recibido nuevamente en el atributo value:

<input
    type="text"
    name="nombre"
    value="<?php echo htmlspecialchars($nombre, ENT_QUOTES, 'UTF-8'); ?>"
>

De esta manera el campo conserva lo que había escrito.

Formulario completo: HTML + PHP + validación

Veamos ahora el ejemplo completo en un único archivo llamado registro.php.

<?php

$nombre = '';
$email = '';
$edad = '';
$mensaje = '';

$errores = [];
$enviado = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $nombre = trim($_POST['nombre'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $edad = trim($_POST['edad'] ?? '');
    $mensaje = trim($_POST['mensaje'] ?? '');

    if ($nombre === '') {
        $errores[] = 'El nombre es obligatorio';
    }

    if ($email === '') {
        $errores[] = 'El email es obligatorio';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errores[] = 'El email no es válido';
    }

    if ($edad === '') {
        $errores[] = 'La edad es obligatoria';
    } elseif (filter_var($edad, FILTER_VALIDATE_INT) === false) {
        $errores[] = 'La edad debe ser un número entero';
    } elseif ((int) $edad < 18) {
        $errores[] = 'Debés ser mayor de edad';
    }

    if ($mensaje === '') {
        $errores[] = 'El mensaje es obligatorio';
    }

    if (empty($errores)) {
        $enviado = true;
    }
}

?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Formulario de registro</title>
</head>
<body>

    <h1>Formulario de registro</h1>

    <?php if ($enviado): ?>

        <h2>Datos recibidos correctamente</h2>

        <p>
            Nombre:
            <?php echo htmlspecialchars($nombre, ENT_QUOTES, 'UTF-8'); ?>
        </p>

        <p>
            Email:
            <?php echo htmlspecialchars($email, ENT_QUOTES, 'UTF-8'); ?>
        </p>

        <p>
            Edad:
            <?php echo htmlspecialchars($edad, ENT_QUOTES, 'UTF-8'); ?>
        </p>

        <p>
            Mensaje:
            <?php echo nl2br(
                htmlspecialchars($mensaje, ENT_QUOTES, 'UTF-8')
            ); ?>
        </p>

    <?php else: ?>

        <?php if (!empty($errores)): ?>

            <ul>
                <?php foreach ($errores as $error): ?>
                    <li>
                        <?php echo htmlspecialchars(
                            $error,
                            ENT_QUOTES,
                            'UTF-8'
                        ); ?>
                    </li>
                <?php endforeach; ?>
            </ul>

        <?php endif; ?>

        <form method="post">

            <div>
                <label for="nombre">Nombre:</label>

                <input
                    type="text"
                    id="nombre"
                    name="nombre"
                    value="<?php echo htmlspecialchars(
                        $nombre,
                        ENT_QUOTES,
                        'UTF-8'
                    ); ?>"
                    required
                >
            </div>

            <br>

            <div>
                <label for="email">Email:</label>

                <input
                    type="email"
                    id="email"
                    name="email"
                    value="<?php echo htmlspecialchars(
                        $email,
                        ENT_QUOTES,
                        'UTF-8'
                    ); ?>"
                    required
                >
            </div>

            <br>

            <div>
                <label for="edad">Edad:</label>

                <input
                    type="number"
                    id="edad"
                    name="edad"
                    min="18"
                    value="<?php echo htmlspecialchars(
                        $edad,
                        ENT_QUOTES,
                        'UTF-8'
                    ); ?>"
                    required
                >
            </div>

            <br>

            <div>
                <label for="mensaje">Mensaje:</label>

                <textarea
                    id="mensaje"
                    name="mensaje"
                    rows="5"
                    required
                ><?php echo htmlspecialchars(
                    $mensaje,
                    ENT_QUOTES,
                    'UTF-8'
                ); ?></textarea>
            </div>

            <br>

            <button type="submit">
                Enviar
            </button>

        </form>

    <?php endif; ?>

</body>
</html>

Analicemos la secuencia de este ejemplo

1. El navegador solicita registro.php.

Como todavía no se envió ningún formulario, la petición normalmente es GET.

GET /registro.php

2. PHP ejecuta el archivo.

La condición:

$_SERVER['REQUEST_METHOD'] === 'POST'

es falsa, por lo que simplemente se muestra el formulario.

3. El usuario completa los campos.

Nombre: Ana
Email: ana@email.com
Edad: 25
Mensaje: Quiero registrarme

4. El usuario presiona Enviar.

El navegador genera una nueva petición, esta vez usando POST:

POST /registro.php

5. PHP recibe los datos.

$_POST['nombre']
$_POST['email']
$_POST['edad']
$_POST['mensaje']

6. PHP valida cada dato.

Si existe algún problema, se agrega un mensaje al array $errores.

7. Si hay errores, el formulario vuelve a mostrarse.

Los valores anteriores se conservan para que el usuario pueda corregir solamente lo necesario.

8. Si no hay errores, se procesa la información.

En nuestro ejemplo simplemente mostramos los datos. Más adelante podríamos:

  • guardarlos en MySQL;
  • enviar un correo;
  • registrar un usuario;
  • crear una reserva;
  • generar una factura;
  • ejecutar cualquier otra lógica de negocio.

¿Dónde termina HTML y dónde empieza PHP?

Una de las particularidades de PHP es que podemos alternar entre HTML y PHP dentro del mismo archivo.

<h1>Bienvenido</h1>

<?php

$nombre = 'Ana';

?>

<p>
    Hola <?php echo htmlspecialchars($nombre); ?>
</p>

El servidor procesa primero el PHP. El navegador recibe solamente el HTML resultante.

El navegador nunca recibe el código PHP

Esto es importante:

Servidor:
registro.php
    ↓
PHP ejecuta el código
    ↓
genera HTML
    ↓
Navegador recibe HTML

El usuario no descarga el código PHP original desde el navegador.

Ejemplo práctico con GET: buscador

<?php

$q = trim($_GET['q'] ?? '');

?>

<form method="get">

    <label for="q">Buscar:</label>

    <input
        type="text"
        id="q"
        name="q"
        value="<?php echo htmlspecialchars(
            $q,
            ENT_QUOTES,
            'UTF-8'
        ); ?>"
    >

    <button type="submit">
        Buscar
    </button>

</form>

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

    <p>
        Buscando resultados para:
        <strong>
            <?php echo htmlspecialchars(
                $q,
                ENT_QUOTES,
                'UTF-8'
            ); ?>
        </strong>
    </p>

<?php endif; ?>

Si buscamos php arrays, la URL podría ser:

buscar.php?q=php+arrays

Esto tiene sentido porque una búsqueda es algo que puede compartirse mediante una URL.

Un error común: confiar solo en required

Este formulario parece validar el nombre:

<input type="text" name="nombre" required>

Pero un cliente HTTP podría enviar directamente:

POST /registro.php

nombre=

sin utilizar nuestro HTML.

Por eso seguimos necesitando:

if (trim($_POST['nombre'] ?? '') === '') {
    $errores[] = 'El nombre es obligatorio';
}

Otro error común: imprimir datos directamente

No conviene hacer esto con información proporcionada por el usuario:

echo $_POST['nombre'];

Es preferible escapar la salida:

echo htmlspecialchars(
    $_POST['nombre'] ?? '',
    ENT_QUOTES,
    'UTF-8'
);

HTMLspecialchars no reemplaza la validación

htmlspecialchars() sirve para mostrar contenido dentro de HTML de manera segura.

No comprueba si:

  • un email es válido;
  • un número está dentro de un rango;
  • una fecha existe;
  • un usuario tiene permisos;
  • un dato cumple las reglas de negocio.

Cada problema necesita su propia validación.

Errores comunes

  • olvidar el atributo name;
  • confundir id con name;
  • usar $_GET cuando el formulario envía por POST;
  • acceder a una clave de $_POST antes de comprobar si existe;
  • confiar solamente en la validación HTML;
  • mostrar datos del usuario sin escaparlos;
  • confundir validación con sanitización;
  • no conservar los valores del formulario después de un error;
  • usar GET para operaciones que modifican información.

Buenas prácticas

  • usar GET para consultas y filtros;
  • usar POST para operaciones que envían o modifican información;
  • validar siempre en el servidor;
  • escapar la salida al imprimir datos dentro de HTML;
  • utilizar mensajes de error claros;
  • mantener los valores que el usuario ya escribió;
  • separar progresivamente la presentación de la lógica cuando el proyecto crece.

Ejercicios

  1. crear un formulario con nombre y apellido usando POST;
  2. mostrar los datos recibidos con PHP;
  3. crear un buscador sencillo usando GET;
  4. agregar un campo email y validarlo con filter_var();
  5. agregar edad y exigir un mínimo de 18 años;
  6. guardar los errores dentro de un array;
  7. mostrar todos los errores con foreach;
  8. mantener los valores escritos cuando hay un error;
  9. agregar un campo select para elegir una provincia;
  10. agregar varios checkbox y observar cómo llegan a PHP;
  11. crear un formulario de contacto completo;
  12. explicar con tus palabras la secuencia navegador → HTTP → PHP → respuesta.

Qué aprendimos

  • cómo construir un formulario HTML;
  • qué hacen action y method;
  • por qué name es fundamental;
  • diferencia entre GET y POST;
  • cómo funcionan $_GET y $_POST;
  • qué son las superglobales;
  • cómo detectar el método HTTP;
  • cómo validar campos;
  • cómo validar emails y números;
  • diferencia entre validación y sanitización;
  • cómo usar htmlspecialchars();
  • cómo conservar valores después de un error;
  • cómo combinar HTML y PHP en un mismo archivo;
  • cómo funciona la secuencia completa de envío de un formulario.

¿Qué sigue?

En la próxima guía podemos profundizar en formularios PHP más completos: select, checkbox, radio buttons, múltiples valores, archivos con $_FILES y validaciones más avanzadas.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

Funciones en PHP: parámetros, return, alcance, tipado y callbacks

Hasta ahora vimos variables, condicionales, bucles y arrays. A medida que un programa crece, aparece un problema muy común: la misma lógica empieza a repetirse en varios lugares.

Las funciones permiten agrupar instrucciones bajo un nombre, reutilizarlas y dividir un programa en partes más pequeñas y comprensibles.

¿Qué es una función?

Una función es un bloque de código que realiza una tarea concreta y que puede ejecutarse cuando sea necesario.

En vez de repetir varias veces:

$precio = 1000;
$iva = $precio * 0.21;
$total = $precio + $iva;

podemos encapsular esa lógica dentro de una función y reutilizarla.

Funciones incorporadas y funciones propias

PHP incluye muchas funciones listas para usar, como:

count()
strlen()
var_dump()
array_filter()
date()

Estas funciones ya vienen con PHP.

Pero también podemos crear nuestras propias funciones con la palabra clave function.

Crear una función

function saludar() {
    echo 'Hola desde una función';
}

Esto define la función, pero todavía no la ejecuta.

Llamar una función

saludar();

Cuando PHP encuentra esa llamada, ejecuta el código que está dentro de la función.

¿Por qué usar funciones?

Las funciones ayudan a:

  • evitar código repetido;
  • dividir problemas grandes en partes pequeñas;
  • mejorar la lectura del código;
  • facilitar pruebas y mantenimiento;
  • reutilizar lógica.

Parámetros

Una función puede recibir valores desde afuera mediante parámetros.

function saludar($nombre) {
    echo 'Hola ' . $nombre;
}

saludar('Ana');

En este caso $nombre es el parámetro de la función.

Parámetros y argumentos

Aunque suelen confundirse, no son exactamente lo mismo:

  • parámetro: variable declarada en la función;
  • argumento: valor que enviamos al llamar la función.

Ejemplo:

function saludar($nombre) {
    // $nombre es parámetro
}

saludar('Ana'); // 'Ana' es argumento

Más de un parámetro

function sumar($a, $b) {
    return $a + $b;
}

La función recibe dos valores y los utiliza dentro de su lógica.

return

return devuelve un valor al lugar desde donde se llamó la función.

function sumar($a, $b) {
    return $a + $b;
}

$resultado = sumar(10, 5);

echo $resultado;

Resultado:

15

Diferencia entre echo y return

Esta diferencia es fundamental.

echo muestra contenido:

function saludar($nombre) {
    echo 'Hola ' . $nombre;
}

return devuelve un valor para poder seguir utilizándolo:

function obtenerSaludo($nombre) {
    return 'Hola ' . $nombre;
}

$mensaje = obtenerSaludo('Ana');

En general, una función es más reutilizable cuando devuelve datos en lugar de imprimirlos directamente.

return termina la función

Cuando PHP ejecuta return, la función finaliza.

function ejemplo() {
    return 10;

    echo 'Esto nunca se ejecuta';
}

Parámetros con valores por defecto

Podemos hacer que un parámetro sea opcional.

function saludar($nombre = 'Invitado') {
    return 'Hola ' . $nombre;
}

echo saludar();
echo saludar('Ana');

Si no enviamos un argumento, se utiliza 'Invitado'.

Los parámetros obligatorios deben ir primero

Es recomendable escribir primero los parámetros obligatorios y luego los opcionales.

function crearUsuario($nombre, $activo = true) {
    // ...
}

Ejemplo: calcular IVA

function calcularIva($importe, $porcentaje = 21) {
    return $importe * $porcentaje / 100;
}

$iva = calcularIva(10000);

echo $iva;

La lógica del cálculo queda centralizada en un único lugar.

Ejemplo: calcular total con IVA

function calcularTotalConIva($importe, $porcentaje = 21) {
    $iva = $importe * $porcentaje / 100;

    return $importe + $iva;
}

$total = calcularTotalConIva(10000);

Alcance de variables

Una variable creada fuera de una función no está disponible automáticamente dentro de ella.

$nombre = 'Ana';

function mostrarNombre() {
    echo $nombre;
}

Ese código no funciona como muchos principiantes esperan, porque $nombre pertenece al ámbito exterior.

Variables locales

Las variables creadas dentro de una función existen solamente dentro de esa función.

function calcular() {
    $resultado = 10 + 5;

    return $resultado;
}

$resultado es una variable local.

La palabra global

PHP permite acceder a una variable externa mediante global.

$iva = 21;

function mostrarIva() {
    global $iva;

    echo $iva;
}

Aunque existe, conviene evitar depender de variables globales siempre que sea posible. Las funciones suelen ser más fáciles de entender y probar cuando reciben los datos mediante parámetros.

Una alternativa mejor a global

$iva = 21;

function calcularImpuesto($importe, $iva) {
    return $importe * $iva / 100;
}

$resultado = calcularImpuesto(10000, $iva);

La dependencia queda explícita.

Variables static dentro de funciones

Una variable local normalmente se crea de nuevo cada vez que llamamos a la función.

Con static podemos conservar su valor entre llamadas.

function contador() {
    static $numero = 0;

    $numero++;

    return $numero;
}

echo contador(); // 1
echo contador(); // 2
echo contador(); // 3

Tipado de parámetros

PHP permite indicar qué tipo esperamos recibir.

function sumar(int $a, int $b) {
    return $a + $b;
}

Esto expresa que ambos parámetros deben representar enteros.

Tipado de retorno

También podemos indicar qué tipo debe devolver la función.

function sumar(int $a, int $b): int {
    return $a + $b;
}

El : int indica el tipo de retorno.

Ejemplo con string

function nombreCompleto(string $nombre, string $apellido): string {
    return $nombre . ' ' . $apellido;
}

Ejemplo con bool

function esMayorDeEdad(int $edad): bool {
    return $edad >= 18;
}

Esta función no imprime un mensaje. Devuelve true o false, lo que permite reutilizarla dentro de otras condiciones.

Nullable types

En algunos casos queremos aceptar un tipo o null.

function mostrarTelefono(?string $telefono): string {
    return $telefono ?? 'Sin teléfono';
}

?string significa:

string o null

Union types

Desde PHP 8 podemos declarar más de un tipo posible utilizando |.

function mostrarId(int|string $id): string {
    return 'ID: ' . $id;
}

La función acepta un entero o un string.

Funciones que devuelven arrays

function crearUsuario(string $nombre, string $email): array {
    return [
        'nombre' => $nombre,
        'email' => $email,
        'activo' => true
    ];
}

Los arrays son un tipo de retorno muy habitual en PHP.

Paso por valor

Por defecto, los valores simples se pasan de forma que modificar el parámetro no altera necesariamente la variable original.

function duplicar($numero) {
    $numero *= 2;
}

$valor = 10;
duplicar($valor);

echo $valor;

El valor exterior sigue siendo 10.

Paso por referencia con &

Podemos indicar que queremos trabajar directamente sobre la variable original utilizando &.

function duplicar(&$numero) {
    $numero *= 2;
}

$valor = 10;
duplicar($valor);

echo $valor;

Ahora el resultado es:

20

El paso por referencia debe utilizarse con cuidado porque la función produce un efecto sobre una variable externa.

Funciones anónimas

Una función no siempre necesita tener un nombre.

$saludar = function ($nombre) {
    return 'Hola ' . $nombre;
};

echo $saludar('Ana');

Este tipo de función es muy utilizado en callbacks.

Callbacks

Un callback es una función que enviamos a otra función para que sea ejecutada.

Ya vimos un ejemplo con array_map():

$numeros = [1, 2, 3, 4];

$dobles = array_map(function ($numero) {
    return $numero * 2;
}, $numeros);

array_map() ejecuta nuestro callback para cada elemento.

Arrow functions

PHP permite escribir funciones anónimas simples de forma más compacta mediante fn.

$numeros = [1, 2, 3, 4];

$dobles = array_map(
    fn($numero) => $numero * 2,
    $numeros
);

Captura de variables en arrow functions

Las arrow functions pueden utilizar variables del ámbito exterior automáticamente.

$multiplicador = 3;

$resultado = array_map(
    fn($numero) => $numero * $multiplicador,
    [1, 2, 3]
);

Funciones anónimas y use

En una función anónima tradicional podemos importar variables externas mediante use.

$iva = 21;

$calcular = function ($importe) use ($iva) {
    return $importe * $iva / 100;
};

Ejemplo práctico: calcular descuento

function calcularDescuento(float $total, bool $premium): float {
    if ($premium && $total >= 100000) {
        return $total * 0.20;
    }

    if ($total >= 100000) {
        return $total * 0.10;
    }

    return 0;
}

La función recibe toda la información necesaria y devuelve únicamente el descuento calculado.

Ejemplo práctico: total de productos

function calcularTotal(array $productos): float {
    $total = 0;

    foreach ($productos as $producto) {
        $total += $producto['precio'] * $producto['cantidad'];
    }

    return $total;
}

Una función debería hacer una tarea clara

En general, una función es más fácil de mantener cuando tiene una responsabilidad bien definida.

Es mejor tener:

calcularSubtotal()
calcularIva()
calcularDescuento()

que una función gigantesca llamada:

procesarTodo()

Nombres descriptivos

El nombre debería explicar qué hace la función.

Buenos ejemplos:

calcularTotal()
esMayorDeEdad()
obtenerNombreCompleto()
filtrarProductosActivos()

Nombres poco claros:

hacer()
procesar()
x()

Errores comunes

  • confundir echo con return;
  • olvidar llamar la función después de crearla;
  • depender demasiado de variables globales;
  • crear funciones que hacen demasiadas cosas;
  • no usar nombres descriptivos;
  • usar paso por referencia sin necesidad;
  • declarar tipos incompatibles con lo que realmente devuelve la función;
  • duplicar lógica en vez de reutilizar una función existente.

Buenas prácticas

  • dar a cada función una responsabilidad clara;
  • preferir parámetros frente a variables globales;
  • devolver datos con return cuando sea posible;
  • usar tipos cuando ayuden a expresar la intención;
  • mantener funciones relativamente pequeñas;
  • elegir nombres que describan la acción.

Ejercicios

  1. crear una función que salude a una persona;
  2. crear una función que sume dos números;
  3. calcular IVA utilizando un parámetro opcional;
  4. crear una función que determine si una persona es mayor de edad;
  5. crear una función que devuelva nombre y apellido unidos;
  6. crear una función que reciba un array y devuelva su suma;
  7. declarar tipos de parámetros y retorno;
  8. crear una función que acepte string|null;
  9. crear una función que acepte un id int|string;
  10. probar paso por valor y paso por referencia;
  11. duplicar un array de números usando array_map() y una función anónima;
  12. reescribir el ejercicio anterior mediante una arrow function;
  13. crear una función para calcular descuentos;
  14. crear una función que calcule el total de un array de productos.

Qué aprendimos

  • qué es una función;
  • funciones incorporadas y propias;
  • parámetros y argumentos;
  • return;
  • diferencia entre echo y return;
  • valores por defecto;
  • alcance de variables;
  • global y static;
  • tipado de parámetros y retorno;
  • nullable y union types;
  • paso por valor y por referencia;
  • funciones anónimas;
  • callbacks;
  • arrow functions.

¿Qué sigue?

En la próxima guía comenzaremos a trabajar con formularios HTML y PHP: métodos GET y POST, las variables $_GET y $_POST, validación y procesamiento de datos enviados por el usuario.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

Arrays en PHP: indexados, asociativos y multidimensionales con ejemplos prácticos

Hasta ahora trabajamos con variables que guardaban un solo valor. Pero en muchas situaciones necesitamos almacenar varios datos relacionados dentro de una misma estructura.

Para eso PHP utiliza los arrays. Son fundamentales porque aparecen constantemente en formularios, resultados de bases de datos, APIs, configuraciones y colecciones de objetos.

¿Qué es un array?

Un array es una estructura que permite guardar varios valores dentro de una misma variable.

En lugar de hacer esto:

$color1 = 'rojo';
$color2 = 'verde';
$color3 = 'azul';

podemos agruparlos:

$colores = ['rojo', 'verde', 'azul'];

Ahora $colores contiene tres elementos.

Array indexado

Un array indexado utiliza posiciones numéricas para identificar cada elemento.

$frutas = ['Manzana', 'Pera', 'Naranja'];

PHP comienza los índices numéricos en 0.

0 → Manzana
1 → Pera
2 → Naranja

Acceder a un elemento

$frutas = ['Manzana', 'Pera', 'Naranja'];

echo $frutas[0];

Resultado:

Manzana

Modificar un elemento

$frutas[1] = 'Banana';

Ahora el array contiene:

Manzana
Banana
Naranja

Agregar elementos

La forma más simple es utilizar corchetes vacíos:

$frutas[] = 'Kiwi';

PHP agrega el nuevo valor al final del array.

Array asociativo

Un array asociativo utiliza claves con significado en lugar de índices numéricos.

$persona = [
    'nombre' => 'Ana',
    'edad' => 25,
    'ciudad' => 'Santa Rosa'
];

En este caso las claves son:

nombre
edad
ciudad

Acceder por clave

echo $persona['nombre'];

Resultado:

Ana

Modificar un valor asociativo

$persona['edad'] = 26;

Agregar una nueva clave

$persona['email'] = 'ana@email.com';

Clave y valor

En un array asociativo cada elemento se compone de dos partes:

clave => valor

Por ejemplo:

'nombre' => 'Ana'

La clave identifica el dato y el valor contiene la información.

Arrays multidimensionales

Un array puede contener otros arrays.

$usuarios = [
    [
        'nombre' => 'Ana',
        'edad' => 25
    ],
    [
        'nombre' => 'Juan',
        'edad' => 31
    ]
];

Esto permite representar estructuras más complejas.

Acceder a un array multidimensional

echo $usuarios[0]['nombre'];

Resultado:

Ana

Primero seleccionamos el elemento 0 del array principal y luego la clave nombre.

Ejemplo real: productos

$productos = [
    [
        'nombre' => 'Teclado',
        'precio' => 25000,
        'stock' => 10
    ],
    [
        'nombre' => 'Mouse',
        'precio' => 15000,
        'stock' => 25
    ]
];

Este formato es muy parecido a la información que luego podemos obtener desde una base de datos.

Recorrer arrays con foreach

foreach es la estructura más cómoda para recorrer arrays.

$frutas = ['Manzana', 'Pera', 'Naranja'];

foreach ($frutas as $fruta) {
    echo $fruta . '
'
; }

Recorrer clave y valor

foreach ($persona as $clave => $valor) {
    echo $clave . ': ' . $valor . '
'
; }

Recorrer arrays multidimensionales

foreach ($productos as $producto) {
    echo $producto['nombre'] . ' - $' . $producto['precio'] . '
'
; }

count()

count() devuelve la cantidad de elementos de un array.

$frutas = ['Manzana', 'Pera', 'Naranja'];

echo count($frutas);

Resultado:

3

in_array()

in_array() permite saber si un valor existe dentro de un array.

$roles = ['admin', 'editor', 'usuario'];

if (in_array('admin', $roles, true)) {
    echo 'Existe el rol admin';
}

El tercer parámetro true solicita comparación estricta.

array_key_exists()

En arrays asociativos podemos comprobar si existe una clave.

if (array_key_exists('email', $persona)) {
    echo $persona['email'];
}

isset() y una diferencia importante

isset() también suele utilizarse para comprobar datos:

if (isset($persona['email']))) {
    echo 'Existe y no es NULL';
}

Pero isset() devuelve falso si la clave existe y su valor es null. array_key_exists(), en cambio, verifica la existencia de la clave independientemente de su valor.

Eliminar elementos con unset()

$frutas = ['Manzana', 'Pera', 'Naranja'];

unset($frutas[1]);

El elemento se elimina, pero PHP no necesariamente reordena automáticamente los índices existentes.

Agregar con array_push()

array_push($frutas, 'Kiwi');

Aunque funciona, para agregar un único elemento suele ser más simple:

$frutas[] = 'Kiwi';

array_pop()

Elimina y devuelve el último elemento.

$ultimo = array_pop($frutas);

array_shift()

Elimina y devuelve el primer elemento.

$primero = array_shift($frutas);

array_unshift()

Agrega uno o más elementos al comienzo.

array_unshift($frutas, 'Frutilla');

Ordenar arrays

sort()

Ordena un array indexado de menor a mayor.

$numeros = [5, 2, 9, 1];

sort($numeros);

rsort()

Ordena en sentido descendente.

rsort($numeros);

Ordenar arrays asociativos

asort() ordena por valor manteniendo la relación con las claves.

$precios = [
    'teclado' => 25000,
    'mouse' => 15000,
    'monitor' => 180000
];

asort($precios);

ksort() ordena por clave.

ksort($precios);

array_merge()

Permite combinar arrays.

$grupo1 = ['Ana', 'Juan'];
$grupo2 = ['Lucía', 'Pedro'];

$todos = array_merge($grupo1, $grupo2);

implode()

implode() convierte un array en un string utilizando un separador.

$nombres = ['Ana', 'Juan', 'Lucía'];

$texto = implode(', ', $nombres);

echo $texto;

Resultado:

Ana, Juan, Lucía

explode()

explode() realiza la operación inversa: separa un string y devuelve un array.

$texto = 'Ana,Juan,Lucía';

$nombres = explode(',', $texto);

array_map()

array_map() permite transformar cada elemento de un array.

$numeros = [1, 2, 3, 4];

$dobles = array_map(function ($numero) {
    return $numero * 2;
}, $numeros);

El resultado será:

[2, 4, 6, 8]

¿Qué es un callback?

En el ejemplo anterior, array_map() recibe una función que PHP ejecutará para cada elemento.

A una función que se entrega a otra función para que sea ejecutada posteriormente se la suele llamar callback.

Más adelante veremos funciones con mayor profundidad.

array_filter()

array_filter() permite conservar únicamente los elementos que cumplen una condición.

$numeros = [1, 2, 3, 4, 5, 6];

$pares = array_filter($numeros, function ($numero) {
    return $numero % 2 === 0;
});

Ejemplo práctico: filtrar productos con stock

$productos = [
    ['nombre' => 'Teclado', 'stock' => 10],
    ['nombre' => 'Mouse', 'stock' => 0],
    ['nombre' => 'Monitor', 'stock' => 5]
];

$disponibles = array_filter($productos, function ($producto) {
    return $producto['stock'] > 0;
});

Ahora $disponibles contiene solamente productos con stock.

Acceso seguro con ??

Cuando una clave puede no existir, podemos usar el operador ??.

$telefono = $persona['telefono'] ?? 'Sin teléfono';

Si la clave no existe o contiene null, se utiliza el valor alternativo.

Arrays y datos de formularios

Cuando trabajemos con formularios veremos variables como:

$_GET
$_POST
$_FILES
$_SESSION

Varias de estas estructuras son arrays asociativos proporcionados por PHP.

Por eso dominar arrays es fundamental antes de avanzar hacia formularios y aplicaciones reales.

Arrays y bases de datos

Cuando consultamos una base de datos desde PHP, es habitual recibir resultados con una estructura similar a:

[
    ['id' => 1, 'nombre' => 'Ana'],
    ['id' => 2, 'nombre' => 'Juan']
]

Luego podemos recorrer esos registros con foreach.

Errores comunes

  • olvidar que los arrays indexados comienzan en 0;
  • intentar acceder a una clave que no existe;
  • confundir índice con valor;
  • usar sort() sobre un array asociativo cuando queremos conservar sus claves;
  • no diferenciar isset() de array_key_exists();
  • crear estructuras multidimensionales innecesariamente complejas;
  • modificar un array sin comprender cómo afecta sus índices.

Buenas prácticas

  • usar claves descriptivas;
  • mantener estructuras consistentes;
  • usar foreach para recorrer colecciones;
  • comprobar claves opcionales antes de acceder;
  • evitar demasiados niveles de anidación;
  • utilizar funciones de arrays cuando expresen mejor la intención del código.

Ejercicios

  1. crear un array con cinco nombres y recorrerlo;
  2. mostrar el tercer elemento de un array indexado;
  3. crear un array asociativo con nombre, edad y email;
  4. agregar una nueva clave al array;
  5. crear un array de productos con nombre, precio y stock;
  6. recorrer productos con foreach;
  7. contar productos utilizando count();
  8. comprobar si existe un valor con in_array();
  9. comprobar una clave con array_key_exists();
  10. ordenar números ascendente y descendentemente;
  11. convertir un array de nombres a texto con implode();
  12. convertir un texto separado por comas en array con explode();
  13. duplicar números utilizando array_map();
  14. filtrar números pares con array_filter();
  15. filtrar productos con stock mayor a cero.

Qué aprendimos

  • qué es un array;
  • arrays indexados;
  • arrays asociativos;
  • arrays multidimensionales;
  • clave y valor;
  • recorrido con foreach;
  • count();
  • in_array();
  • array_key_exists();
  • isset();
  • ordenamiento;
  • array_merge();
  • implode() y explode();
  • array_map() y array_filter();
  • callbacks;
  • relación de los arrays con formularios y bases de datos.

¿Qué sigue?

En la próxima guía veremos funciones en PHP: cómo crear nuestras propias funciones, parámetros, valores de retorno, alcance de variables y tipado.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

Bucles en PHP: for, while, do while y foreach con ejemplos prácticos

Muchas tareas de programación requieren repetir instrucciones: mostrar una lista, recorrer productos, sumar valores, buscar un dato o ejecutar una acción varias veces.

En PHP estas repeticiones se realizan mediante bucles. En esta guía vamos a aprender cómo funcionan for, while, do while y foreach, cuándo conviene usar cada uno y cómo controlar su ejecución.

¿Qué es un bucle?

Un bucle es una estructura que permite ejecutar un bloque de código varias veces.

Por ejemplo, si queremos mostrar los números del 1 al 5, podríamos escribir:

echo 1;
echo 2;
echo 3;
echo 4;
echo 5;

Pero eso es repetitivo. Un bucle permite expresar la misma idea de forma más compacta y flexible.

Dos formas comunes de repetir

Podemos pensar los bucles de dos maneras principales:

  • por cantidad: repetir un número conocido de veces;
  • por condición: repetir mientras algo siga siendo verdadero.

Por ejemplo:

  • mostrar del 1 al 10 → cantidad conocida;
  • seguir leyendo hasta encontrar un dato → depende de una condición.

El bucle for

for es ideal cuando sabemos cuántas veces queremos repetir algo.

for ($i = 1; $i <= 5; $i++) {
    echo $i;
}

Este bucle muestra:

12345

Las tres partes del for

La estructura general es:

for (inicialización; condición; actualización) {
    // instrucciones
}

En:

for ($i = 1; $i <= 5; $i++)

tenemos:

  • $i = 1: valor inicial;
  • $i <= 5: condición para seguir;
  • $i++: incremento después de cada vuelta.

¿Qué es una iteración?

Cada repetición completa del bucle se denomina iteración.

En un for del 1 al 5 hay cinco iteraciones.

Mostrar números del 1 al 10

for ($i = 1; $i <= 10; $i++) {
    echo $i . ' ';
}

Contar hacia atrás

for ($i = 10; $i >= 1; $i--) {
    echo $i . ' ';
}

En este caso utilizamos $i-- para disminuir el contador.

Tabla de multiplicar

$numero = 7;

for ($i = 1; $i <= 10; $i++) {
    $resultado = $numero * $i;
    echo "$numero x $i = $resultado" . '
'
; }

El contador cambia en cada iteración y permite calcular una fila diferente.

Contadores y acumuladores

Un contador normalmente aumenta o disminuye para controlar repeticiones.

$contador = 0;
$contador++;

Un acumulador guarda un resultado que se va construyendo.

$suma = 0;

for ($i = 1; $i <= 100; $i++) {
    $suma += $i;
}

echo $suma;

En cada vuelta sumamos el valor actual de $i al acumulador.

El bucle while

while repite un bloque mientras una condición sea verdadera.

$i = 1;

while ($i <= 5) {
    echo $i;
    $i++;
}

Antes de cada iteración, PHP evalúa la condición.

¿Cuándo conviene while?

while es útil cuando no sabemos exactamente cuántas repeticiones serán necesarias.

Por ejemplo:

  • esperar hasta encontrar un valor;
  • procesar datos mientras existan;
  • repetir una operación hasta alcanzar una condición.

El peligro de un bucle infinito

Si la condición nunca se vuelve falsa, el bucle puede ejecutarse indefinidamente.

$i = 1;

while ($i <= 5) {
    echo $i;
}

Este ejemplo es incorrecto porque $i nunca cambia.

La versión correcta sería:

$i = 1;

while ($i <= 5) {
    echo $i;
    $i++;
}

do while

do while es parecido a while, pero garantiza que el bloque se ejecute al menos una vez.

$i = 1;

do {
    echo $i;
    $i++;
} while ($i <= 5);

Diferencia entre while y do while

Con while la condición se evalúa antes de ejecutar el bloque.

Con do while la condición se evalúa después.

while → evalúa → ejecuta

do while → ejecuta → evalúa

Por eso do while siempre ejecuta al menos una iteración.

Ejemplo donde do while ejecuta una vez

$numero = 10;

do {
    echo 'Se ejecutó una vez';
} while ($numero < 5);

Aunque la condición sea falsa desde el principio, el bloque se ejecuta una vez.

foreach

foreach está diseñado especialmente para recorrer arrays y estructuras iterables.

$nombres = ['Ana', 'Juan', 'Lucía'];

foreach ($nombres as $nombre) {
    echo $nombre . '
'
; }

En cada iteración, $nombre toma el valor de un elemento del array.

¿Qué significa as?

La palabra as indica qué variable utilizar para representar el elemento actual.

foreach ($nombres as $nombre)

Puede leerse como:

“por cada elemento de $nombres, guardalo temporalmente en $nombre”.

foreach con clave y valor

También podemos obtener la clave de cada elemento.

$persona = [
    'nombre' => 'Ana',
    'edad' => 25,
    'ciudad' => 'Santa Rosa'
];

foreach ($persona as $clave => $valor) {
    echo $clave . ': ' . $valor . '
'
; }

En cada vuelta:

  • $clave recibe la clave;
  • $valor recibe el contenido.

Recorrer productos

$productos = [
    ['nombre' => 'Teclado', 'precio' => 25000],
    ['nombre' => 'Mouse', 'precio' => 15000],
    ['nombre' => 'Monitor', 'precio' => 180000]
];

foreach ($productos as $producto) {
    echo $producto['nombre'] . ' - $' . $producto['precio'] . '
'
; }

Este tipo de estructura es muy común al trabajar con resultados de bases de datos o APIs.

break

break permite finalizar un bucle antes de que termine normalmente.

for ($i = 1; $i <= 10; $i++) {
    if ($i === 5) {
        break;
    }

    echo $i . ' ';
}

Resultado:

1 2 3 4

Buscar un valor con break

$numeros = [3, 8, 12, 20, 25];
$buscado = 12;

foreach ($numeros as $numero) {
    if ($numero === $buscado) {
        echo 'Encontrado';
        break;
    }
}

Una vez encontrado el valor, no tiene sentido seguir recorriendo el array.

continue

continue salta la iteración actual y pasa a la siguiente.

for ($i = 1; $i <= 10; $i++) {
    if ($i === 5) {
        continue;
    }

    echo $i . ' ';
}

Resultado:

1 2 3 4 6 7 8 9 10

Ejemplo: mostrar solo números pares

for ($i = 1; $i <= 20; $i++) {
    if ($i % 2 !== 0) {
        continue;
    }

    echo $i . ' ';
}

Los números impares se saltan y solamente se muestran los pares.

Bucles anidados

Un bucle puede contener otro bucle.

for ($fila = 1; $fila <= 3; $fila++) {
    for ($columna = 1; $columna <= 3; $columna++) {
        echo "($fila,$columna) ";
    }

    echo '
'
; }

Por cada iteración del bucle exterior, el bucle interior se ejecuta completo.

¿Cuántas veces se ejecuta un bucle anidado?

Si el bucle exterior se ejecuta 3 veces y el interior 4 veces, el bloque interior se ejecuta:

3 × 4 = 12 veces

Esto es importante porque los bucles anidados pueden aumentar rápidamente el trabajo realizado.

for, while, do while o foreach

Una guía rápida:

  • for: cuando tenemos un contador o cantidad conocida;
  • while: cuando repetimos mientras una condición sea verdadera;
  • do while: cuando necesitamos ejecutar al menos una vez;
  • foreach: cuando recorremos arrays u objetos iterables.

Ejemplo completo: total de una compra

$productos = [
    ['nombre' => 'Teclado', 'precio' => 25000, 'cantidad' => 2],
    ['nombre' => 'Mouse', 'precio' => 15000, 'cantidad' => 1],
    ['nombre' => 'Monitor', 'precio' => 180000, 'cantidad' => 1]
];

$total = 0;

foreach ($productos as $producto) {
    $subtotal = $producto['precio'] * $producto['cantidad'];
    $total += $subtotal;

    echo $producto['nombre'] . ' - $' . $subtotal . '
'
; } echo 'Total: $' . $total;

En este ejemplo usamos:

  • un array de productos;
  • foreach para recorrerlo;
  • una variable $subtotal;
  • un acumulador $total.

Errores comunes

  • crear un bucle infinito;
  • olvidar incrementar el contador en un while;
  • usar for cuando foreach sería más claro;
  • confundir break con continue;
  • modificar accidentalmente variables de control;
  • crear demasiados niveles de bucles anidados;
  • usar nombres poco descriptivos para los elementos recorridos.

Buenas prácticas

  • usar nombres claros como $producto en lugar de $x;
  • mantener simple la condición del bucle;
  • evitar trabajo innecesario dentro de cada iteración;
  • usar break cuando ya no sea necesario continuar;
  • preferir foreach para recorrer arrays.

Ejercicios

  1. mostrar los números del 1 al 20 con for;
  2. mostrar los números del 20 al 1;
  3. sumar los números del 1 al 100;
  4. mostrar una tabla de multiplicar;
  5. resolver los ejercicios anteriores utilizando while;
  6. crear un ejemplo donde do while se ejecute una sola vez;
  7. recorrer un array de nombres con foreach;
  8. recorrer un array asociativo mostrando clave y valor;
  9. buscar un número y detener el recorrido con break;
  10. mostrar únicamente números pares utilizando continue;
  11. crear una matriz 3 × 3 mediante bucles anidados;
  12. calcular el total de un listado de productos.

Qué aprendimos

  • qué es un bucle;
  • qué es una iteración;
  • cómo funciona for;
  • contadores y acumuladores;
  • while;
  • do while;
  • foreach;
  • clave y valor;
  • break;
  • continue;
  • bucles anidados;
  • cuándo conviene cada estructura.

¿Qué sigue?

En la próxima guía profundizaremos en arrays en PHP: arrays indexados, asociativos, multidimensionales y las funciones más importantes para trabajar con ellos.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

Condicionales en PHP: if, elseif, else, switch, match y operador ternario

Los programas no siempre ejecutan exactamente las mismas instrucciones. Muchas veces necesitan tomar decisiones según los datos disponibles: permitir o negar acceso, aplicar un descuento, mostrar un mensaje distinto o ejecutar una acción determinada.

En PHP estas decisiones se construyen mediante condicionales. En esta guía vamos a aprender cómo funcionan if, elseif, else, switch, match y el operador ternario.

¿Qué es una condición?

Una condición es una expresión que PHP puede evaluar como verdadera o falsa.

Por ejemplo:

$edad = 20;

$edad >= 18

La expresión $edad >= 18 produce un valor booleano:

true

Ese resultado puede utilizarse para decidir qué bloque de código ejecutar.

Recordatorio: operadores de comparación

Las condiciones suelen construirse con operadores como:

  • ==: igualdad flexible;
  • ===: igualdad estricta;
  • !=: distinto;
  • !==: distinto en valor o tipo;
  • >: mayor;
  • <: menor;
  • >=: mayor o igual;
  • <=: menor o igual.

La estructura if

if ejecuta un bloque de código solamente cuando una condición es verdadera.

<?php

$edad = 20;

if ($edad >= 18) {
    echo 'Es mayor de edad';
}

PHP evalúa primero:

$edad >= 18

Como el resultado es true, ejecuta el contenido entre llaves.

¿Qué representan las llaves?

Las llaves { } delimitan el bloque de instrucciones que pertenece al condicional.

if ($condicion) {
    echo 'Primera instrucción';
    echo 'Segunda instrucción';
}

Si la condición es falsa, ninguna de esas instrucciones se ejecuta.

if y else

else permite definir qué debe ocurrir cuando la condición del if es falsa.

$edad = 16;

if ($edad >= 18) {
    echo 'Es mayor de edad';
} else {
    echo 'Es menor de edad';
}

Solo uno de los dos bloques será ejecutado.

if, elseif y else

Cuando existen más de dos posibilidades podemos utilizar elseif.

$nota = 8;

if ($nota >= 9) {
    echo 'Excelente';
} elseif ($nota >= 6) {
    echo 'Aprobado';
} else {
    echo 'Desaprobado';
}

PHP evalúa las condiciones de arriba hacia abajo y ejecuta el primer bloque cuya condición sea verdadera.

El orden de las condiciones importa

Este ejemplo está mal planteado:

if ($nota >= 6) {
    echo 'Aprobado';
} elseif ($nota >= 9) {
    echo 'Excelente';
}

Si la nota es 10, la primera condición ya es verdadera y PHP nunca llega al segundo bloque.

Por eso conviene ordenar primero las condiciones más específicas.

Condiciones compuestas

Podemos combinar varias condiciones mediante operadores lógicos.

AND con &&

$edad = 20;
$tieneDocumento = true;

if ($edad >= 18 && $tieneDocumento === true) {
    echo 'Puede ingresar';
}

Ambas condiciones deben ser verdaderas.

OR con ||

$esAdmin = false;
$esSupervisor = true;

if ($esAdmin || $esSupervisor) {
    echo 'Tiene permisos';
}

Alcanza con que una de las dos condiciones sea verdadera.

NOT con !

$bloqueado = false;

if (!$bloqueado) {
    echo 'Usuario habilitado';
}

El operador ! invierte un valor booleano.

Usar paréntesis para expresar la lógica

Cuando combinamos varias condiciones, los paréntesis ayudan a dejar claro el orden de evaluación.

if (($edad >= 18 && $tieneDocumento) || $esAdmin) {
    echo 'Acceso permitido';
}

La intención resulta mucho más clara que en una expresión larga sin agrupación visual.

Comparación estricta dentro de condicionales

Cuando queremos comparar no solo el valor sino también el tipo, utilizamos ===.

$estado = '1';

if ($estado === 1) {
    echo 'Activo';
}

Este bloque no se ejecuta porque '1' es un string y 1 es un entero.

Valores truthy y falsy

PHP puede interpretar ciertos valores como verdaderos o falsos incluso si no son booleanos explícitos.

Por ejemplo, estos valores suelen evaluarse como falsos:

  • false;
  • 0;
  • 0.0;
  • '';
  • '0';
  • null;
  • un array vacío.

Ejemplo:

$nombre = '';

if ($nombre) {
    echo 'Hay un nombre';
} else {
    echo 'El nombre está vacío';
}

Esto puede ser útil, pero también puede generar confusión. Cuando el tipo importa, conviene escribir condiciones explícitas.

switch

switch resulta útil cuando queremos comparar una misma variable contra varios valores posibles.

$estado = 'pendiente';

switch ($estado) {
    case 'pendiente':
        echo 'Pedido pendiente';
        break;

    case 'pagado':
        echo 'Pedido pagado';
        break;

    case 'cancelado':
        echo 'Pedido cancelado';
        break;

    default:
        echo 'Estado desconocido';
}

¿Para qué sirve break?

break detiene la ejecución del switch una vez encontrada una coincidencia.

Si lo omitimos, PHP puede continuar ejecutando los siguientes casos. Este comportamiento se denomina fall-through.

default

default funciona como un caso alternativo cuando ningún case coincide.

Es comparable al else de una estructura if.

¿Cuándo conviene switch?

Puede ser más legible que muchos elseif cuando comparamos una misma variable contra valores concretos.

Por ejemplo, estados como:

pendiente
pagado
cancelado
enviado

son un buen caso de uso.

match en PHP moderno

Desde PHP 8 existe match, una expresión más moderna para resolver ciertos casos similares a switch.

$estado = 'pagado';

$mensaje = match ($estado) {
    'pendiente' => 'Pedido pendiente',
    'pagado' => 'Pedido pagado',
    'cancelado' => 'Pedido cancelado',
    default => 'Estado desconocido',
};

echo $mensaje;

match devuelve un valor, por eso podemos asignar directamente su resultado a una variable.

Diferencias importantes entre switch y match

match tiene algunas ventajas:

  • utiliza comparación estricta;
  • no necesita break;
  • devuelve un valor;
  • suele ser más compacto.

Para código PHP moderno suele ser una alternativa muy clara cuando necesitamos mapear un valor a otro.

Operador ternario

El operador ternario permite expresar una decisión simple en una sola expresión.

$edad = 20;

$mensaje = $edad >= 18 ? 'Mayor de edad' : 'Menor de edad';

echo $mensaje;

Su estructura es:

condición ? valor_si_true : valor_si_false

¿Cuándo usar el ternario?

Conviene utilizarlo para decisiones breves y fáciles de leer.

Si la condición se vuelve compleja, un if suele ser más claro.

Ternario corto

PHP también permite:

$nombreMostrado = $nombre ?: 'Sin nombre';

Si $nombre es evaluado como verdadero, se utiliza su valor. De lo contrario, se utiliza 'Sin nombre'.

Ternario corto vs operador ??

No hacen exactamente lo mismo.

$valor = $nombre ?: 'Sin nombre';
$valor = $nombre ?? 'Sin nombre';

?: utiliza el valor alternativo cuando el primero es falsy.

?? utiliza el alternativo solamente cuando la variable no existe o es null.

Ejemplo: sistema de descuentos

$total = 150000;
$esClientePremium = true;

if ($esClientePremium && $total >= 100000) {
    $descuento = 20;
} elseif ($total >= 100000) {
    $descuento = 10;
} else {
    $descuento = 0;
}

echo "Descuento: $descuento%";

Acá combinamos una condición numérica con una condición booleana.

Ejemplo: permisos de usuario

$rol = 'admin';
$activo = true;

if ($activo && $rol === 'admin') {
    echo 'Acceso completo';
} elseif ($activo && $rol === 'editor') {
    echo 'Acceso de edición';
} else {
    echo 'Acceso limitado';
}

Errores comunes

  • confundir = con == o ===;
  • ordenar mal los elseif;
  • olvidar break dentro de un switch;
  • escribir condiciones demasiado largas;
  • abusar del operador ternario;
  • no tener en cuenta los valores truthy y falsy;
  • usar comparaciones flexibles cuando el tipo importa.

Ejercicios

  1. determinar si una persona es mayor de edad;
  2. indicar si un número es positivo, negativo o cero;
  3. clasificar una nota como desaprobada, aprobada o excelente;
  4. determinar si un número es par utilizando %;
  5. crear un sistema de descuento según importe;
  6. mostrar un mensaje diferente según el rol del usuario;
  7. resolver un estado de pedido con switch;
  8. reescribir ese mismo ejemplo utilizando match;
  9. mostrar “Activo” o “Inactivo” mediante un ternario;
  10. comparar el comportamiento de ?: y ??.

Qué aprendimos

  • qué es una condición;
  • cómo funciona if;
  • else y elseif;
  • condiciones compuestas;
  • operadores lógicos;
  • truthy y falsy;
  • switch y break;
  • match;
  • operador ternario;
  • diferencias entre ?: y ??.

¿Qué sigue?

En la próxima guía veremos bucles en PHP: for, while, do while y foreach, para repetir instrucciones de forma controlada.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal

Variables, tipos de datos y operadores en PHP: guía completa desde cero

En PHP, casi cualquier programa necesita guardar valores, operar con ellos y tomar decisiones a partir de esos datos. Para eso utilizamos variables, tipos de datos y operadores.

Esta guía parte desde cero y explica no solo la sintaxis, sino también qué representa cada concepto y por qué PHP se comporta de determinada manera.

¿Qué es una variable?

Una variable es un nombre que utilizamos para referirnos a un valor durante la ejecución de un programa.

Por ejemplo, en vez de escribir el nombre de una persona muchas veces podemos guardarlo una sola vez:

<?php

$nombre = 'Ana';

echo $nombre;

En este ejemplo:

  • $nombre es la variable;
  • = es el operador de asignación;
  • 'Ana' es el valor almacenado.

¿Por qué las variables comienzan con $?

En PHP todas las variables comienzan con el símbolo $.

$nombre
$edad
$precio
$activo

El símbolo forma parte de la sintaxis del lenguaje y permite identificar rápidamente una variable.

Asignar un valor

El operador = asigna el valor de la derecha a la variable de la izquierda.

$edad = 25;

No significa “es igual a” en el sentido matemático. Significa:

guardá el valor 25 dentro de la variable $edad.

Reglas para nombrar variables

Un nombre de variable:

  • debe comenzar con $;
  • después del símbolo debe comenzar con una letra o guion bajo;
  • puede contener números después del primer carácter;
  • no puede contener espacios;
  • distingue mayúsculas y minúsculas.

Ejemplos válidos:

$nombre
$nombreCompleto
$_usuario
$precio2

Ejemplos incorrectos:

$2precio
$nombre completo

PHP distingue mayúsculas y minúsculas en variables

Estas dos variables son diferentes:

$nombre = 'Ana';
$Nombre = 'Juan';

Por claridad, suele utilizarse camelCase:

$nombreCompleto
$precioFinal
$cantidadProductos

¿Qué es un tipo de dato?

El tipo indica qué clase de información representa un valor.

No es lo mismo guardar:

25
'25'
true
25.50

Aunque algunos valores parezcan similares, PHP puede tratarlos de manera diferente.

Tipos de datos básicos en PHP

String

Un string representa texto.

$nombre = 'Ana';
$ciudad = "Santa Rosa";

Integer

Un int representa números enteros.

$edad = 25;
$cantidad = 10;

Float

Un float representa números con decimales.

$precio = 1250.50;
$altura = 1.78;

Boolean

Un bool solo puede representar verdadero o falso.

$activo = true;
$bloqueado = false;

Los booleanos son fundamentales para condiciones que veremos en la próxima guía.

NULL

null representa ausencia de valor.

$telefono = null;

No significa cero ni una cadena vacía. Significa que la variable no posee actualmente un valor útil.

Array

Un array permite almacenar varios valores dentro de una misma variable.

$colores = ['rojo', 'verde', 'azul'];

Más adelante dedicaremos una guía completa a arrays.

PHP tiene tipado dinámico

En PHP no necesitamos declarar de antemano el tipo de una variable en los casos simples.

$dato = 10;
$dato = 'Ahora soy texto';

La misma variable primero contiene un entero y luego un string.

Esto se conoce como tipado dinámico: el tipo está asociado al valor en tiempo de ejecución.

¿Qué significa que PHP tenga coerción de tipos?

PHP puede convertir automáticamente ciertos valores cuando una operación lo requiere.

$numero = '10';
$resultado = $numero + 5;

echo $resultado;

En este caso PHP puede interpretar el string '10' como número y producir:

15

Esta flexibilidad puede ser útil, pero también producir errores difíciles de detectar. Por eso conviene entender siempre con qué tipos estamos trabajando.

Funciones incorporadas de PHP

PHP incluye una gran cantidad de funciones listas para usar. Estas funciones forman parte del propio lenguaje o de sus extensiones estándar y permiten realizar tareas frecuentes sin tener que programarlas desde cero.

Algunos ejemplos son:

  • var_dump(): permite inspeccionar una variable y ver su tipo y valor;
  • gettype(): devuelve el tipo de dato de una variable;
  • strlen(): obtiene la longitud de un texto;
  • count(): cuenta los elementos de un array;
  • date(): permite trabajar con fechas y formatos de fecha.

Estas son funciones propias de PHP y podemos utilizarlas directamente. Más adelante también veremos cómo crear nuestras propias funciones mediante la palabra clave function.

Podés consultar muchas más funciones y su documentación en el Manual oficial de PHP: referencia de funciones.

var_dump()

var_dump() muestra el tipo y el valor de una variable.

$edad = 25;

var_dump($edad);

El resultado será similar a:

int(25)

Con un string:

$nombre = 'Ana';
var_dump($nombre);

podríamos obtener:

string(3) "Ana"

gettype()

gettype() devuelve el nombre del tipo.

$precio = 1500.50;

echo gettype($precio);

Resultado:

double

En PHP, los valores que normalmente llamamos float pueden aparecer como double en ciertas funciones históricas.

Conversión explícita de tipos

También podemos indicar manualmente el tipo que queremos.

$texto = '25';
$edad = (int) $texto;

Otros casts habituales son:

(int)
(float)
(string)
(bool)
(array)

Strings: comillas simples y dobles

PHP permite escribir strings con comillas simples o dobles.

$nombre = 'Ana';

echo 'Hola $nombre';

Con comillas simples, PHP muestra literalmente:

Hola $nombre

En cambio:

echo "Hola $nombre";

produce:

Hola Ana

Interpolación de variables

La posibilidad de incluir una variable dentro de un string con comillas dobles se llama interpolación.

$nombre = 'Ana';
$edad = 25;

echo "$nombre tiene $edad años";

Concatenación con .

PHP utiliza el punto . para unir strings.

$nombre = 'Ana';

echo 'Hola ' . $nombre;

Resultado:

Hola Ana

Operadores aritméticos

Permiten realizar cálculos.

  • + suma;
  • - resta;
  • * multiplicación;
  • / división;
  • % resto;
  • ** potencia.
$a = 10;
$b = 3;

echo $a + $b;
echo $a - $b;
echo $a * $b;
echo $a / $b;
echo $a % $b;
echo $a ** $b;

El operador módulo %

El operador módulo devuelve el resto de una división.

10 % 3

devuelve:

1

Es muy útil, por ejemplo, para comprobar si un número es par:

$numero = 8;

$resto = $numero % 2;

Si el resto es cero, el número es divisible por 2.

Operadores de asignación

Podemos combinar una operación con la asignación.

$numero = 10;

$numero += 5;

Es equivalente a:

$numero = $numero + 5;

También existen:

+=
-=
*=
/=
%=

Incremento y decremento

$contador = 1;

$contador++;
$contador--;

++ aumenta en uno y -- disminuye en uno.

Operadores de comparación

Los operadores de comparación producen un valor booleano: true o false.

== igualdad flexible

var_dump(10 == '10');

Puede devolver true porque PHP convierte tipos para comparar.

=== igualdad estricta

var_dump(10 === '10');

Devuelve false porque uno es entero y el otro string.

Por qué suele preferirse ===

La comparación estricta exige que coincidan valor y tipo.

Esto evita resultados inesperados producidos por conversiones automáticas.

Otros operadores de comparación

  • !=: distinto;
  • !==: distinto en valor o tipo;
  • >: mayor;
  • <: menor;
  • >=: mayor o igual;
  • <=: menor o igual.

Operador spaceship <=>

El operador <=> compara dos valores y devuelve:

  • -1 si el primero es menor;
  • 0 si son equivalentes;
  • 1 si el primero es mayor.
$resultado = 10  20;

echo $resultado;

Resultado:

-1

Operadores lógicos

Permiten combinar condiciones booleanas.

  • &&: AND;
  • ||: OR;
  • !: NOT.
$mayorDeEdad = true;
$tienePermiso = false;

$puedeEntrar = $mayorDeEdad && $tienePermiso;

El resultado será false porque ambas condiciones deberían ser verdaderas.

Operador ??

El operador de coalescencia nula ?? permite usar un valor alternativo cuando una variable o clave no existe o contiene NULL.

$nombre = null;

echo $nombre ?? 'Sin nombre';

Resultado:

Sin nombre

Más adelante resulta especialmente útil al trabajar con formularios.

Precedencia de operadores

PHP no evalúa todas las operaciones simplemente de izquierda a derecha. Algunos operadores tienen mayor prioridad.

$resultado = 2 + 3 * 4;

El resultado es:

14

porque primero se realiza la multiplicación.

Si queremos cambiar el orden usamos paréntesis:

$resultado = (2 + 3) * 4;

Ahora el resultado es:

20

Ejemplo completo

<?php

$nombre = 'Ana';
$edad = 25;
$precio = 1250.50;
$cantidad = 3;
$activo = true;

$total = $precio * $cantidad;

echo "Cliente: $nombre" . '\n';
echo "Edad: $edad" . '\n';
echo "Total: $total";

En este ejemplo:

  1. creamos variables de distintos tipos;
  2. multiplicamos precio por cantidad;
  3. guardamos el resultado en otra variable;
  4. mostramos los valores mediante echo.

Buenas prácticas iniciales

  • usar nombres descriptivos;
  • evitar variables como $x salvo casos muy simples;
  • preferir comparaciones estrictas cuando corresponda;
  • usar paréntesis cuando una expresión pueda resultar ambigua;
  • comprobar tipos con var_dump() durante el aprendizaje y depuración.

Ejercicios

  1. crear variables para nombre, edad y ciudad y mostrarlas;
  2. calcular el área de un rectángulo;
  3. calcular el total de una compra usando precio y cantidad;
  4. convertir un string numérico a entero;
  5. comparar 10 == '10' y 10 === '10';
  6. comprobar el resto de dividir un número por 2;
  7. usar ?? para mostrar un valor alternativo;
  8. crear una expresión que combine operadores aritméticos y paréntesis.

Qué aprendimos

  • qué es una variable;
  • cómo funciona $;
  • qué son los tipos de datos;
  • strings, enteros, floats, booleanos, NULL y arrays;
  • tipado dinámico y coerción;
  • var_dump() y gettype();
  • concatenación e interpolación;
  • operadores aritméticos;
  • operadores de comparación;
  • operadores lógicos;
  • ??;
  • precedencia de operadores.

¿Qué sigue?

En la próxima guía veremos condicionales en PHP: if, elseif, else, switch y operadores ternarios.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal