Artículo original de febrero de 2002. Actualizado en mayo de 2026 por David Carrero. Las funciones mysql_* que se usaban en el original fueron eliminadas definitivamente en PHP 7.0 (diciembre de 2015). Este artículo conserva el enfoque original para contexto histórico y añade la versión moderna con PDO.
PHP y MySQL: una combinación que sigue dominando la web
En 2002, cuando se escribió este artículo, la combinación de PHP y MySQL era ya la opción más popular para crear sitios dinámicos. Dos décadas después sigue siéndolo: PHP mueve el 77% de los servidores con lenguaje detectado, y MySQL es el motor de bases de datos de código abierto más usado del mundo. La diferencia está en cómo se hace la conexión.
El código original usaba las funciones mysql_* de PHP 4: mysql_connect(), mysql_query(), mysql_fetch_row(). Esas funciones se marcaron como obsoletas en PHP 5.5 (2013) y se eliminaron por completo en PHP 7.0 (diciembre de 2015). Si tu código todavía las usa, simplemente no funciona en ningún servidor moderno.
La sustitución oficial son MySQLi (extensión mejorada, solo MySQL) o PDO (agnóstica de base de datos). En este artículo usaremos PDO porque funciona con MySQL, PostgreSQL, SQLite y otros motores sin cambiar la lógica de acceso a datos.
La estructura de la base de datos (igual que en 2002)
Seguimos con el ejemplo original: una tabla usuarios sencilla. Lo que ha cambiado es cómo se crea y cómo se accede a ella.
CREATE DATABASE IF NOT EXISTS ejemplo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE ejemplo;
CREATE TABLE IF NOT EXISTS usuarios (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(50) NOT NULL,
apellido VARCHAR(50) NOT NULL,
dni VARCHAR(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Dos diferencias respecto al original: usamos utf8mb4 (soporte completo para Unicode, incluidos emojis) e InnoDB como motor de almacenamiento, que admite transacciones y claves foráneas.
Código histórico de 2002 (solo referencia)
El artículo original usaba cinco archivos con las funciones mysql_*. Lo reproducimos aquí para comparar, pero no lo uses en proyectos reales:
<?php // conexion.php - PHP 4/5, NO compatible con PHP 7+ $dbhost = "localhost"; $dbusuario = "agustin"; $dbpassword = "mipass"; $db = "ejemplo"; $conexion = mysql_connect($dbhost, $dbusuario, $dbpassword); // Eliminado en PHP 7.0 mysql_select_db($db, $conexion); ?>
El problema más grave del código original no era solo usar funciones obsoletas: los valores del formulario se insertaban directamente en las consultas sin escapar ni validar, lo que abría una inyección SQL clásica. Por ejemplo:
<?php
// Código vulnerable del original (no usar)
$result = mysql_query("INSERT INTO usuarios (id, nombre, apellido, dni)
VALUES ('', $nombre, $apellido, $dni)", $conexion);
?>
Si $nombre valía x', '', ''); DROP TABLE usuarios; --, la consulta eliminaba la tabla completa. Con PDO y sentencias preparadas ese ataque es imposible por diseño.
Versión moderna con PDO (PHP 8)
Conexión reutilizable
<?php
// conexion.php PHP 8 con PDO
function obtenerConexion(): PDO
{
static $pdo = null;
if ($pdo === null) {
$pdo = new PDO(
'mysql:host=localhost;dbname=ejemplo;charset=utf8mb4',
'agustin',
'mipass',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
}
return $pdo;
}
?>
La instancia se crea solo una vez gracias a la variable estática. PDO::ATTR_EMULATE_PREPARES => false garantiza que las sentencias preparadas son preparadas de verdad en el servidor MySQL, no simuladas por el driver PHP.
Guardar un registro nuevo
<?php
require 'conexion.php';
function guardarUsuario(string $nombre, string $apellido, string $dni): int
{
$pdo = obtenerConexion();
$stmt = $pdo->prepare(
"INSERT INTO usuarios (nombre, apellido, dni) VALUES (:nombre, :apellido, :dni)"
);
$stmt->execute([':nombre' => $nombre, ':apellido' => $apellido, ':dni' => $dni]);
return (int) $pdo->lastInsertId();
}
// Formulario de alta
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = trim($_POST['nombre'] ?? '');
$apellido = trim($_POST['apellido'] ?? '');
$dni = trim($_POST['dni'] ?? '');
if ($nombre && $apellido && $dni) {
$id = guardarUsuario($nombre, $apellido, $dni);
echo "<p>Usuario guardado con ID: $id</p>";
}
}
?>
<form method="post">
<label>Nombre: <input type="text" name="nombre" required></label><br>
<label>Apellido: <input type="text" name="apellido" required></label><br>
<label>DNI: <input type="text" name="dni" required></label><br>
<button type="submit">Guardar</button>
</form>
Ver todos los registros
<?php
require 'conexion.php';
function listarUsuarios(): array
{
return obtenerConexion()
->query("SELECT id, nombre, apellido, dni FROM usuarios ORDER BY apellido, nombre")
->fetchAll();
}
$usuarios = listarUsuarios();
?>
<table>
<thead>
<tr><th>Nombre</th><th>Apellido</th><th>DNI</th><th></th></tr>
</thead>
<tbody>
<?php foreach ($usuarios as $u): ?>
<tr>
<td><?= htmlspecialchars($u['nombre']) ?></td>
<td><?= htmlspecialchars($u['apellido']) ?></td>
<td><?= htmlspecialchars($u['dni']) ?></td>
<td><a href="actualizar.php?id=<?= $u['id'] ?>">Editar</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
Fíjate en htmlspecialchars() al renderizar los datos. Aunque los hemos guardado correctamente, siempre hay que escapar los valores al imprimirlos en HTML para evitar XSS.
Actualizar un registro
<?php
require 'conexion.php';
function obtenerUsuario(int $id): ?array
{
$stmt = obtenerConexion()->prepare(
"SELECT id, nombre, apellido, dni FROM usuarios WHERE id = :id"
);
$stmt->execute([':id' => $id]);
$row = $stmt->fetch();
return $row ?: null;
}
function actualizarUsuario(int $id, string $nombre, string $apellido, string $dni): bool
{
$stmt = obtenerConexion()->prepare(
"UPDATE usuarios SET nombre=:nombre, apellido=:apellido, dni=:dni WHERE id=:id"
);
$stmt->execute([':nombre' => $nombre, ':apellido' => $apellido, ':dni' => $dni, ':id' => $id]);
return $stmt->rowCount() > 0;
}
$id = (int) ($_GET['id'] ?? 0);
if (!$id) { header('Location: ver.php'); exit; }
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$ok = actualizarUsuario(
$id,
trim($_POST['nombre']),
trim($_POST['apellido']),
trim($_POST['dni'])
);
if ($ok) { header('Location: ver.php'); exit; }
}
$usuario = obtenerUsuario($id);
if (!$usuario) { http_response_code(404); echo 'Usuario no encontrado'; exit; }
?>
<form method="post">
<label>Nombre: <input type="text" name="nombre" value="<?= htmlspecialchars($usuario['nombre']) ?>" required></label><br>
<label>Apellido: <input type="text" name="apellido" value="<?= htmlspecialchars($usuario['apellido']) ?>" required></label><br>
<label>DNI: <input type="text" name="dni" value="<?= htmlspecialchars($usuario['dni']) ?>" required></label><br>
<button type="submit">Guardar cambios</button>
</form>
Eliminar un registro
El artículo original no incluía la operación de borrado. La añadimos aquí para completar el CRUD:
<?php
require 'conexion.php';
function eliminarUsuario(int $id): bool
{
$stmt = obtenerConexion()->prepare("DELETE FROM usuarios WHERE id = :id");
$stmt->execute([':id' => $id]);
return $stmt->rowCount() > 0;
}
// Solo aceptar POST para operaciones destructivas
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int) ($_POST['id'] ?? 0);
if ($id && eliminarUsuario($id)) {
header('Location: ver.php');
exit;
}
}
?>
Gestión de errores
PDO lanza excepciones cuando algo falla, así que puedes capturarlas donde las necesites:
<?php
try {
$id = guardarUsuario('Ana', 'López', '12345678A');
echo "Guardado con ID: $id";
} catch (PDOException $e) {
// En producción: log($e->getMessage()), mostrar mensaje genérico
error_log('DB error: ' . $e->getMessage());
echo 'Ha ocurrido un error al guardar los datos.';
}
?>
Nunca muestres el mensaje de la excepción directamente al usuario en producción: puede revelar la estructura de tu base de datos.
Del código de 2002 a PHP 8: resumen de diferencias
PHP 4/5 (original) | PHP 8 con PDO |
|
|
Variables directas en SQL | Sentencias preparadas con |
Sin protección contra SQL injection | Imposible por diseño |
Error silencioso o | Excepciones |
Solo MySQL | MySQL, PostgreSQL, SQLite, Oracle |
Eliminado en PHP 7.0 (2015) | Soportado en PHP 5.1+ y PHP 8 |
Si buscas cómo almacenar imágenes o ficheros binarios en MySQL con PHP 8, consulta el artículo Manejo de datos BLOB con PHP y MySQL. Para proteger las contraseñas de tus usuarios, lee Contraseñas seguras en PHP: de MD5 a bcrypt y Argon2.
Imagen: Pexels / Pixabay
