charset=utf8mb4, modo de
errores por excepciones y FETCH_ASSOC por defecto. Las credenciales no van en el código que
subes a GitHub, y nunca muestres el error de conexión al usuario.
Una aplicación full stack sin base de datos es una maqueta. El momento en que PHP habla con MySQL es cuando tu proyecto empieza a recordar usuarios, productos y pedidos. En el libro Programación Full-Stack este paso abre la segunda mitad del libro: todo lo que sigue (consultas seguras, login, CRUD) parte de una buena conexión.
PDO o mysqli: ¿cuál elegir?
PHP ofrece dos extensiones modernas para MySQL. Las dos admiten consultas preparadas, que es lo importante. Las diferencias:
| PDO | mysqli | |
|---|---|---|
| Bases de datos | MySQL, PostgreSQL, SQLite, SQL Server y más | Solo MySQL / MariaDB |
Parámetros con nombre (:email) |
Sí | No, solo ? |
| Estilo | Orientado a objetos | Objetos o procedural |
Para empezar recomiendo PDO: su API es más cómoda y lo que aprendas te sirve si mañana cambias de motor de base de datos.
Crea la base de datos y una tabla
Con XAMPP en marcha, abre http://localhost/phpmyadmin, ve a la pestaña SQL y ejecuta:
CREATE DATABASE tienda
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE tienda;
CREATE TABLE productos (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(120) NOT NULL,
precio DECIMAL(10,2) NOT NULL,
stock INT UNSIGNED NOT NULL DEFAULT 0,
creado_en TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO productos (nombre, precio, stock) VALUES
('Teclado mecánico', 45000.00, 10),
('Mouse inalámbrico', 18000.00, 25),
('Monitor 24"', 210000.00, 4);
Usa utf8mb4 (no utf8): es el que admite todos los caracteres, incluidos los
emojis. Y para dinero, DECIMAL, nunca FLOAT, que acumula errores de redondeo.
El archivo de conexión
<?php
declare(strict_types=1);
function db(): PDO
{
static $pdo = null; // una sola conexión por petición
if ($pdo === null) {
$dsn = 'mysql:host=localhost;dbname=tienda;charset=utf8mb4';
$pdo = new PDO($dsn, 'root', '', [
// Lanza excepciones en vez de fallar en silencio
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// Devuelve arrays asociativos: $fila['nombre']
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
// Consultas preparadas reales en el servidor MySQL
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
return $pdo;
}
En XAMPP el usuario por defecto es root sin contraseña. Sirve en tu máquina, pero en
producción crea un usuario propio para la aplicación, con contraseña fuerte y solo los permisos que
necesite.
Maneja el error de conexión sin filtrar datos
<?php
require_once __DIR__ . '/config/db.php';
try {
$pdo = db();
} catch (PDOException $e) {
error_log($e->getMessage()); // el detalle, al log del servidor
http_response_code(500);
exit('Servicio no disponible. Intenta más tarde.'); // al usuario, un mensaje genérico
}
echo $e->getMessage() en producción: el mensaje puede revelar el host, el
nombre de la base de datos o el usuario.
Consultar datos
<?php
require_once __DIR__ . '/config/db.php';
// Sin datos externos: query() es suficiente
$productos = db()->query('SELECT id, nombre, precio FROM productos ORDER BY nombre')->fetchAll();
// Con datos del usuario: SIEMPRE consulta preparada
$stmt = db()->prepare('SELECT * FROM productos WHERE id = :id');
$stmt->execute(['id' => (int) ($_GET['id'] ?? 0)]);
$producto = $stmt->fetch(); // array o false si no existe
?>
<ul>
<?php foreach ($productos as $p): ?>
<li><?= htmlspecialchars($p['nombre']) ?> – $<?= number_format((float) $p['precio'], 2, ',', '.') ?></li>
<?php endforeach; ?>
</ul>
La diferencia entre query() y prepare() no es un detalle: es la frontera entre
una aplicación segura y una vulnerable. Lo explicamos a fondo en
cómo prevenir la inyección SQL en PHP.
Dónde guardar las credenciales
Las contraseñas de la base de datos no deberían estar escritas dentro del código que versionas. Dos opciones sencillas:
- Un archivo
config.local.phpque devuelve un array con las credenciales y que añades a.gitignore. - Variables de entorno, leídas con
getenv('DB_PASS'), configuradas en el panel de tu hosting.
<?php
// Este archivo NO se sube al repositorio
return [
'host' => 'localhost',
'name' => 'tienda',
'user' => 'tienda_app',
'pass' => 'una-contraseña-larga-y-única',
];
Errores comunes
SQLSTATE[HY000] [2002]: MySQL no está en marcha o el host es incorrecto. Arráncalo desde el panel de XAMPP.[1045] Access denied: usuario o contraseña incorrectos.[1049] Unknown database: la base de datos no existe o el nombre tiene una errata.- Tildes y eñes rotas: falta
charset=utf8mb4en el DSN o la tabla se creó con otra codificación. - Abrir una conexión nueva en cada función: reutiliza la misma, como hace la función
db().
De la conexión a una aplicación con datos reales
En el libro Programación Full-Stack, la conexión a MySQL es el punto de partida del login, el CRUD y la tienda online que construyes paso a paso.
Comprar en Amazon