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


Descubre más desde Club Programador

Suscríbete y recibe las últimas entradas en tu correo electrónico.

Deja un comentario

Descubre más desde Club Programador

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo