Leccion 72 de 75 15 min de lectura

Bases de Datos con PDO

Las aplicaciones reales necesitan almacenar datos de forma permanente. PDO (PHP Data Objects) es la forma moderna y segura de trabajar con bases de datos en PHP.

Que es PDO

PDO es una capa de abstraccion para acceder a bases de datos. Funciona con MySQL, PostgreSQL, SQLite y muchos otros sistemas. Su principal ventaja es que puedes cambiar de base de datos sin reescribir todo tu codigo.

Importante

Esta leccion es una introduccion para que conozcas los conceptos basicos. En nuestros cursos avanzados profundizamos en bases de datos con proyectos reales.

Conectar a la base de datos

Para conectar necesitas un DSN (Data Source Name) que especifica el tipo de base de datos y como acceder a ella.

PHP
<?php

declare(strict_types=1);

// Conexion a MySQL
$dsn = 'mysql:host=localhost;dbname=mi_base;charset=utf8mb4';
$usuario = 'root';
$password = 'secreto';

try {
    $pdo = new PDO($dsn, $usuario, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
    echo 'Conexion exitosa';
} catch (PDOException $e) {
    echo 'Error de conexion: ' . $e->getMessage();
}

Las opciones que configuramos son importantes:

  • ERRMODE_EXCEPTION: Lanza excepciones en lugar de errores silenciosos
  • FETCH_ASSOC: Devuelve arrays asociativos por defecto

Conexion a SQLite

SQLite es ideal para practicar porque no requiere servidor. La base de datos es un simple archivo.

PHP
<?php

declare(strict_types=1);

// Conexion a SQLite (crea el archivo si no existe)
$pdo = new PDO('sqlite:mi_base.db', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

// Crear una tabla
$pdo->exec('
    CREATE TABLE IF NOT EXISTS usuarios (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        nombre TEXT NOT NULL,
        email TEXT NOT NULL UNIQUE
    )
');

Consultas con prepared statements

Nunca insertes datos directamente en las consultas SQL. Usa siempre prepared statements para prevenir inyeccion SQL.

PHP
<?php

declare(strict_types=1);

// MAL: Vulnerable a inyeccion SQL
// $pdo->query("SELECT * FROM usuarios WHERE id = $id");

// BIEN: Prepared statement con parametro
$stmt = $pdo->prepare('SELECT * FROM usuarios WHERE id = :id');
$stmt->execute(['id' => 1]);
$usuario = $stmt->fetch();

if ($usuario !== false) {
    echo $usuario['nombre'];
}

Insertar datos

PHP
<?php

declare(strict_types=1);

$stmt = $pdo->prepare('
    INSERT INTO usuarios (nombre, email)
    VALUES (:nombre, :email)
');

$stmt->execute([
    'nombre' => 'Ana Garcia',
    'email' => 'ana@ejemplo.com',
]);

// Obtener el ID del registro insertado
$nuevoId = $pdo->lastInsertId();
echo "Usuario creado con ID: $nuevoId";

Obtener multiples registros

PHP
<?php

declare(strict_types=1);

// Obtener todos los registros
$stmt = $pdo->query('SELECT * FROM usuarios');
$usuarios = $stmt->fetchAll();

foreach ($usuarios as $usuario) {
    echo "{$usuario['nombre']} - {$usuario['email']}\n";
}

// Con condicion
$stmt = $pdo->prepare('SELECT * FROM usuarios WHERE nombre LIKE :busqueda');
$stmt->execute(['busqueda' => '%ana%']);
$resultados = $stmt->fetchAll();

Actualizar y eliminar

PHP
<?php

declare(strict_types=1);

// Actualizar
$stmt = $pdo->prepare('UPDATE usuarios SET email = :email WHERE id = :id');
$stmt->execute([
    'email' => 'nuevo@ejemplo.com',
    'id' => 1,
]);

echo "Registros actualizados: " . $stmt->rowCount();

// Eliminar
$stmt = $pdo->prepare('DELETE FROM usuarios WHERE id = :id');
$stmt->execute(['id' => 1]);

echo "Registros eliminados: " . $stmt->rowCount();

Ejemplo completo: repositorio de usuarios

Un patron comun es encapsular las operaciones de base de datos en una clase:

PHP
<?php

declare(strict_types=1);

class UsuarioRepository
{
    public function __construct(
        private readonly PDO $pdo
    ) {}

    public function buscarPorId(int $id): ?array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM usuarios WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $resultado = $stmt->fetch();

        return $resultado !== false ? $resultado : null;
    }

    public function todos(): array
    {
        $stmt = $this->pdo->query('SELECT * FROM usuarios ORDER BY nombre');
        return $stmt->fetchAll();
    }

    public function crear(string $nombre, string $email): int
    {
        $stmt = $this->pdo->prepare('
            INSERT INTO usuarios (nombre, email) VALUES (:nombre, :email)
        ');
        $stmt->execute(['nombre' => $nombre, 'email' => $email]);

        return (int) $this->pdo->lastInsertId();
    }

    public function actualizar(int $id, string $nombre, string $email): bool
    {
        $stmt = $this->pdo->prepare('
            UPDATE usuarios SET nombre = :nombre, email = :email WHERE id = :id
        ');
        $stmt->execute(['id' => $id, 'nombre' => $nombre, 'email' => $email]);

        return $stmt->rowCount() > 0;
    }

    public function eliminar(int $id): bool
    {
        $stmt = $this->pdo->prepare('DELETE FROM usuarios WHERE id = :id');
        $stmt->execute(['id' => $id]);

        return $stmt->rowCount() > 0;
    }
}

// Uso
$pdo = new PDO('sqlite:mi_base.db');
$repo = new UsuarioRepository($pdo);

$id = $repo->crear('Carlos', 'carlos@ejemplo.com');
$usuario = $repo->buscarPorId($id);
print_r($usuario);

Ejercicios

Ejercicio 1: Conexion y creacion de tabla

Crea una conexion SQLite y una tabla para almacenar productos con campos: id, nombre, precio y stock.

Ver solucion
PHP
<?php

declare(strict_types=1);

$pdo = new PDO('sqlite:tienda.db', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$pdo->exec('
    CREATE TABLE IF NOT EXISTS productos (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        nombre TEXT NOT NULL,
        precio REAL NOT NULL,
        stock INTEGER NOT NULL DEFAULT 0
    )
');

echo 'Tabla productos creada correctamente';

Ejercicio 2: Insertar y consultar

Inserta 3 productos en la tabla y luego muestra todos los que tengan un precio mayor a 10.

Ver solucion
PHP
<?php

declare(strict_types=1);

$pdo = new PDO('sqlite:tienda.db', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

// Insertar productos
$stmt = $pdo->prepare('
    INSERT INTO productos (nombre, precio, stock) VALUES (:nombre, :precio, :stock)
');

$productos = [
    ['nombre' => 'Cuaderno', 'precio' => 5.50, 'stock' => 100],
    ['nombre' => 'Mochila', 'precio' => 25.00, 'stock' => 30],
    ['nombre' => 'Lapicero', 'precio' => 1.50, 'stock' => 200],
];

foreach ($productos as $producto) {
    $stmt->execute($producto);
}

echo "Productos insertados\n\n";

// Consultar productos con precio > 10
$stmt = $pdo->prepare('SELECT * FROM productos WHERE precio > :precio');
$stmt->execute(['precio' => 10]);
$caros = $stmt->fetchAll();

echo "Productos con precio mayor a 10:\n";
foreach ($caros as $p) {
    echo "- {$p['nombre']}: {$p['precio']} euros\n";
}

Ejercicio 3: Actualizar stock

Crea una funcion que reduzca el stock de un producto. Debe devolver false si no hay suficiente stock.

Ver solucion
PHP
<?php

declare(strict_types=1);

function reducirStock(PDO $pdo, int $productoId, int $cantidad): bool
{
    // Obtener stock actual
    $stmt = $pdo->prepare('SELECT stock FROM productos WHERE id = :id');
    $stmt->execute(['id' => $productoId]);
    $producto = $stmt->fetch();

    if ($producto === false) {
        return false; // Producto no existe
    }

    if ($producto['stock'] < $cantidad) {
        return false; // Stock insuficiente
    }

    // Actualizar stock
    $stmt = $pdo->prepare('
        UPDATE productos SET stock = stock - :cantidad WHERE id = :id
    ');
    $stmt->execute(['cantidad' => $cantidad, 'id' => $productoId]);

    return true;
}

// Uso
$pdo = new PDO('sqlite:tienda.db', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

if (reducirStock($pdo, 1, 5)) {
    echo 'Stock reducido correctamente';
} else {
    echo 'No se pudo reducir el stock';
}

Te está gustando el curso?

Tenemos cursos premium con proyectos reales y soporte personalizado.

Descubrir cursos premium