Conexión segura
Por qué usar PDO en lugar de mysqli
PDO (PHP Data Objects) es la capa de abstracción recomendada para conectar PHP con bases de datos. A diferencia de mysqli, PDO permite cambiar de motor sin reescribir el código: MySQL, PostgreSQL, SQLite y otros usan la misma interfaz.
La ventaja principal son las consultas preparadas: separan el SQL de los datos y evitan la inyección SQL de raíz. Con PDO también puedes elegir entre excepciones, errores silenciosos o arrays asociativos sin cambiar la lógica de negocio.
Cadena de conexión y configuración básica
<?php
declare(strict_types=1);
function conectar(): PDO
{
$host = 'localhost';
$dbname = 'mi_aplicacion';
$username = 'usuario';
$password = 'contraseña';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$opciones = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
return new PDO($dsn, $username, $password, $opciones);
}
La cadena DSN incluye el driver (`mysql:`), el host, el nombre de la base de datos y el charset. `utf8mb4` es obligatorio si usas emojis o caracteres especiales. Las opciones configuran PDO para que lance excepciones, devuelva arrays asociativos y use consultas preparadas reales en lugar de emuladas.
PDO vs mysqli: diferencias clave
| Característica | PDO | mysqli |
|---|---|---|
| Soporte de motores | MySQL, PostgreSQL, SQLite, Oracle, etc. | Solo MySQL |
| Consultas preparadas | Sí, con parámetros con nombre o posicionales | Sí, con marcadores de posición |
| Modo de errores | Excepciones, warnings, silencioso | Modo report o excepciones |
| Fetch modes | ASSOC, OBJ, BOTH, CLASS, LAZY, etc. | ASSOC, OBJ, BOTH, NUM |
| API orientada a objetos | Sí, completa | Sí, con versión procedural |
| Portabilidad | Alta: cambias el DSN y funciona | Baja: solo MySQL/MariaDB |
Para proyectos nuevos o migraciones futuras, PDO es la opción recomendada por PHP-FIG y la documentación oficial.
Consultas preparadas con parámetros con nombre
<?php
function obtenerUsuarioPorEmail(PDO $pdo, string $email): array|false
{
$sql = 'SELECT id, nombre, email FROM usuarios WHERE email = :email';
$stmt = $pdo->prepare($sql);
$stmt->execute([':email' => $email]);
return $stmt->fetch();
}
function insertarUsuario(PDO $pdo, string $nombre, string $email): int
{
$sql = 'INSERT INTO usuarios (nombre, email) VALUES (:nombre, :email)';
$stmt = $pdo->prepare($sql);
$stmt->execute([':nombre' => $nombre, ':email' => $email]);
return (int) $pdo->lastInsertId();
}
Los parámetros con nombre (`:email`, `:nombre`) hacen el código más legible que los marcadores posicionales (`?`). La inyección SQL queda descartada porque los valores nunca se concatenan con el SQL. Cada valor se envía por separado al motor.
CRUD completo con PDO
<?php
class UsuarioRepositorio
{
public function __construct(private PDO $pdo) {}
public function listar(): array
{
$stmt = $this->pdo->query('SELECT id, nombre, email FROM usuarios ORDER BY id');
return $stmt->fetchAll();
}
public function obtener(int $id): array|false
{
$sql = 'SELECT id, nombre, email FROM usuarios WHERE id = :id';
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $id]);
return $stmt->fetch();
}
public function crear(string $nombre, string $email): int
{
$sql = 'INSERT INTO usuarios (nombre, email) VALUES (:nombre, :email)';
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':nombre' => $nombre, ':email' => $email]);
return (int) $this->pdo->lastInsertId();
}
public function actualizar(int $id, string $nombre, string $email): bool
{
$sql = 'UPDATE usuarios SET nombre = :nombre, email = :email WHERE id = :id';
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([':id' => $id, ':nombre' => $nombre, ':email' => $email]);
}
public function eliminar(int $id): bool
{
$sql = 'DELETE FROM usuarios WHERE id = :id';
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([':id' => $id]);
}
}
Este CRUD encapsula cada operación en un método independiente. El constructor recibe la conexión PDO por inyección, lo que facilita pruebas y reutilización. Cada método prepara la consulta, ejecuta con los parámetros y devuelve el resultado con el tipo correcto.
Pasos para integrar PDO en tu proyecto
- Confirma que PHP tiene el driver pdo_mysql instalado con php -m | findstr pdo.
- Crea una función o clase que devuelva una instancia de PDO con las opciones recomendadas.
- Define el DSN con utf8mb4 y el modo de error ERRORMODE_EXCEPTION.
- Usa consultas preparadas con parámetros con nombre en todas las operaciones con datos externos.
- Captura las excepciones PDOException para mostrar errores sin exponer detalles internos.
- Cierra la conexión automáticamente: PDO no necesita cierre manual, pero asignar null libera el recurso.
Manejo de errores con excepciones
<?php
try {
$pdo = conectar();
$repo = new UsuarioRepositorio($pdo);
$usuarios = $repo->listar();
} catch (PDOException $e) {
error_log('Error de base de datos: ' . $e->getMessage());
http_response_code(500);
echo 'Error interno. Intenta de nuevo más tarde.';
exit;
}
Configurar `ERRMODE_EXCEPTION` hace que PDO lance `PDOException` en cualquier fallo: conexión rechazada, SQL mal formado, restricciones de clave duplicada. Captura la excepción en un bloque try-catch, registra el error técnico en el log y muestra un mensaje genérico al usuario. Nunca expongas el mensaje real de la excepción en producción.
Preguntas frecuentes
¿Qué necesito instalar para usar PDO con MySQL?
PHP debe tener la extensión pdo_mysql habilitada. En Ubuntu: sudo apt install php-mysql. En Windows: descomenta extension=pdo_mysql en php.ini. Verifica con php -m.
¿PDO es más lento que mysqli?
La diferencia es mínima en aplicaciones reales. La seguridad que aportan las consultas preparadas compensa cualquier micro-optimización.
¿Debo cerrar la conexión PDO manualmente?
No es obligatorio. PDO cierra la conexión al finalizar el script. Si necesitas liberar recursos antes, asigna null a la variable: $pdo = null.
¿Puedo usar PDO con consultas sin preparar?
Puedes usar query() para consultas sin parámetros externos. Para cualquier valor que venga del usuario, usa prepare() con execute().
Fuentes consultadas
Fuentes y referencias
Consulta referencias útiles para ampliar o contrastar esta guía.
Siguiente lectura
Guias relacionadas para continuar
Si quieres seguir con el mismo tema, aqui tienes paginas cercanas que amplian la tecnica, comparan variantes o te llevan a la siguiente rutina.