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.
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
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 silenciososFETCH_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
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
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
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
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
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
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
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
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
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';
}
Has encontrado un error o tienes una sugerencia para mejorar esta leccion?
EscribenosTe está gustando el curso?
Tenemos cursos premium con proyectos reales y soporte personalizado.
Descubrir cursos premium