Configura PDO como se hace en producción, elimina estructuralmente la inyección SQL y haz fiables tus escrituras con transacciones.
Abrir esta lección en KodokonPDO es la interfaz unificada de acceso a bases de datos: MySQL, PostgreSQL o SQLite se manejan todas con la misma API. Tres ajustes separan una conexión ingenua de una de producción: ERRMODE_EXCEPTION para que cada error SQL lance una excepción en lugar de fallar en silencio, FETCH_ASSOC por defecto para obtener resultados limpios, y EMULATE_PREPARES puesto en false para que las sentencias preparadas las ejecute realmente el servidor. Añade charset=utf8mb4 directamente en el DSN - es el único lugar fiable para declararlo.
<?php
declare(strict_types=1);
$dsn = 'mysql:host=localhost;dbname=shop'
. ';charset=utf8mb4';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, 'app_user', 'secret', $options);La sentencia preparada es tu única defensa estructural contra la inyección SQL. El principio: la estructura de la consulta, con sus marcadores :email o ?, se envía primero al servidor; los datos llegan por separado y nunca se interpretan como SQL. Concatenar una variable en una consulta, aunque esté "escapada", sigue siendo un error de profesional. Los marcadores con nombre hacen el código legible; los posicionales bastan para consultas cortas.
<?php
declare(strict_types=1);
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION,
);
$pdo->exec(
'CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL
)'
);
$insert = $pdo->prepare(
'INSERT INTO users (email) VALUES (:email)'
);
$insert->execute(['email' => 'lea@example.com']);
$query = $pdo->prepare(
'SELECT id, email FROM users
WHERE email = :email'
);
$query->execute(['email' => 'lea@example.com']);
var_dump($query->fetch(PDO::FETCH_ASSOC));Una transacción garantiza la atomicidad: o todas las escrituras tienen éxito, o ninguna. El patrón canónico: beginTransaction(), el trabajo dentro de un try, commit() al final del bloque, rollBack() en el catch - y luego relanzas la excepción, porque ocultar el fallo sería peor que el propio fallo. Mantén las transacciones cortas: cada bloqueo retenido durante una llamada de red o un proceso largo degrada la concurrencia de toda la aplicación.
<?php
declare(strict_types=1);
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION,
);
$pdo->exec(
'CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL
)'
);
$pdo->exec(
'INSERT INTO accounts (balance)
VALUES (100), (20)'
);
$pdo->beginTransaction();
try {
$debit = $pdo->prepare(
'UPDATE accounts
SET balance = balance - :n
WHERE id = :id'
);
$debit->execute(['n' => 50, 'id' => 1]);
$credit = $pdo->prepare(
'UPDATE accounts
SET balance = balance + :n
WHERE id = :id'
);
$credit->execute(['n' => 50, 'id' => 2]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
echo 'Transfer done';:param en una sentencia preparada?catch?commit() para guardar lo que tuvo éxitorollBack(), y luego relanzar la excepción