¿Has aprendido PHP básico pero no sabes cómo conectarlo con una base de datos? ¿Te confunde todo eso de MySQL, PDO, mysqli, y no sabes por dónde empezar?
Este tutorial está hecho para ti. Vamos a crear un sistema CRUD completo (Crear, Leer, Actualizar, Eliminar) que realmente funcione. Al final tendrás una aplicación web donde puedes gestionar datos como un profesional.
Lo mejor: Sin tecnicismos innecesarios. Solo código que funciona y explicaciones que entiendes.
🤔 ¿Qué vamos a aprender?
🎯 Al terminar este tutorial sabrás:
📦 ¿Qué es CRUD?
CRUD son las 4 operaciones básicas que puedes hacer con datos:
Es como tener una agenda de contactos: puedes agregar nuevos contactos, ver los contactos, editar información, y eliminar contactos que no necesitas.
🛠️ Preparando el entorno
1. Instalar XAMPP
XAMPP es tu mejor amigo - instala PHP, MySQL y Apache de una vez:
- Descarga XAMPP desde https://www.apachefriends.org
- Instala XAMPP (siguiente, siguiente, instalar)
- Abre el Control Panel de XAMPP
- Inicia Apache y MySQL (botones Start)
💡 Consejo pro:
Si Apache no inicia, probablemente tienes Skype abierto. Cierra Skype y vuelve a intentar.
2. Crear la base de datos
Abre tu navegador y ve a http://localhost/phpmyadmin
- Haz clic en "Nueva" (a la izquierda)
- Nombre de la base: mi_primer_crud
- Haz clic en "Crear"
¡Perfecto! Ya tienes tu base de datos.
🗄️ Creando la tabla
Vamos a crear una tabla para gestionar usuarios. En PhpMyAdmin, dentro de tu base de datos:
CREATE TABLE usuarios (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
edad INT(11),
fecha_registro TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Pega este código en la pestaña "SQL" y haz clic en "Ejecutar".
❌ Error común:
Si ves un error de sintaxis, revisa que hayas copiado TODO el código, incluyendo el punto y coma final.
🔌 Conectando PHP con MySQL
⚠️ IMPORTANTE - Seguridad de credenciales:
NUNCA pongas credenciales de base de datos directamente en tu código. Te voy a enseñar la forma correcta usando variables de entorno.
🔒 Método SEGURO: Variables de entorno
1. Instalar vlucas/phpdotenv (manejador de variables de entorno para PHP):
// En la terminal, dentro de tu carpeta mi_crud:
composer require vlucas/phpdotenv
2. Crear archivo .env (aquí van las credenciales reales):
# Configuración de base de datos
DB_HOST=localhost
DB_USERNAME=root
DB_PASSWORD=
DB_DATABASE=mi_primer_crud
DB_CHARSET=utf8
3. Crear archivo .env.example (plantilla para otros desarrolladores):
# Copia este archivo como .env y completa con tus credenciales reales
DB_HOST=localhost
DB_USERNAME=tu_usuario
DB_PASSWORD=tu_password
DB_DATABASE=nombre_base_datos
DB_CHARSET=utf8
4. Crear archivo .gitignore (evitar subir credenciales):
# Nunca subir credenciales a Git
.env
vendor/
5. Crear archivo conexion.php SEGURO:
<?php
// ✅ Cargar variables de entorno
require_once 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
// ✅ Configuración SEGURA desde variables de entorno
$host = $_ENV['DB_HOST'];
$usuario_db = $_ENV['DB_USERNAME'];
$password_db = $_ENV['DB_PASSWORD'];
$nombre_db = $_ENV['DB_DATABASE'];
$charset = $_ENV['DB_CHARSET'];
try {
// ✅ PDO con configuración desde variables de entorno
$pdo = new PDO(
"mysql:host=$host;dbname=$nombre_db;charset=$charset",
$usuario_db,
$password_db,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
echo "✅ Conexión segura establecida!";
} catch(PDOException $e) {
error_log("Error de conexión: " . $e->getMessage());
die("Error de conexión a la base de datos");
}
?>
🔒 ¿Por qué usar variables de entorno?
- Seguridad: Las credenciales nunca aparecen en el código
- Flexibilidad: Diferentes credenciales por entorno (desarrollo/producción)
- Control de versiones: .env no se sube a Git
- Colaboración: Cada desarrollador tiene sus propias credenciales
- Producción: El servidor puede inyectar variables sin tocar código
🚀 Alternativa simple para principiantes
Si no puedes usar Composer, aquí tienes una versión básica (solo para aprendizaje local):
<?php
// ❌ SOLO para aprendizaje local - NUNCA en producción
$host = "localhost";
$usuario_db = "root";
$password_db = "";
$nombre_db = "mi_primer_crud";
try {
$pdo = new PDO(
"mysql:host=$host;dbname=$nombre_db;charset=utf8",
$usuario_db,
$password_db
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Conexión exitosa!";
} catch(PDOException $e) {
die("Error de conexión: " . $e->getMessage());
}
?>
⚠️ Importante:
La versión simple es SOLO para aprender en tu ordenador local. Para cualquier proyecto real, usa SIEMPRE variables de entorno.
Verifica que funcione: Ve a http://localhost/mi_crud/conexion.php
Si ves "Conexión exitosa!" - ¡perfecto! Si no, revisa que MySQL esté ejecutándose en XAMPP.
📝 CREATE: Insertar datos
Crear archivo insertar.php:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Agregar Usuario</title>
<style>
body { font-family: Arial; margin: 20px; }
form { max-width: 400px; }
input, button { margin: 10px 0; padding: 8px; width: 100%; }
button { background: #007cba; color: white; border: none; }
</style>
</head>
<body>
<h2>Agregar Nuevo Usuario</h2>
<?php
// ✅ Procesar el formulario
if ($_POST) {
include 'conexion.php';
$nombre = $_POST['nombre'];
$email = $_POST['email'];
$edad = $_POST['edad'];
try {
// ✅ Prepared statements = SEGURIDAD
$sql = "INSERT INTO usuarios (nombre, email, edad) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$nombre, $email, $edad]);
echo "<p style='color: green;'>✅ Usuario agregado correctamente!</p>";
} catch(PDOException $e) {
echo "<p style='color: red;'>❌ Error: " . $e->getMessage() . "</p>";
}
}
?>
<form method="POST">
<label>Nombre:</label>
<input type="text" name="nombre" required>
<label>Email:</label>
<input type="email" name="email" required>
<label>Edad:</label>
<input type="number" name="edad" min="1">
<button type="submit">Agregar Usuario</button>
</form>
<p><a href="mostrar.php">Ver todos los usuarios</a></p>
</body>
</html>
💡 ¿Por qué usar Prepared Statements?
- Seguridad: Previene inyección SQL
- Limpio: PHP se encarga de escapar los datos
- Rápido: MySQL puede reutilizar la consulta
👀 READ: Mostrar datos
Crear archivo mostrar.php:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Lista de Usuarios</title>
<style>
body { font-family: Arial; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
th { background-color: #f2f2f2; }
.acciones a { margin-right: 10px; padding: 5px 10px; text-decoration: none; border-radius: 3px; }
.editar { background: #28a745; color: white; }
.eliminar { background: #dc3545; color: white; }
</style>
</head>
<body>
<h2>Lista de Usuarios</h2>
<?php
include 'conexion.php';
try {
// ✅ Obtener todos los usuarios
$sql = "SELECT * FROM usuarios ORDER BY id DESC";
$stmt = $pdo->query($sql);
$usuarios = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($usuarios) > 0) {
echo "<table>";
echo "<tr><th>ID</th><th>Nombre</th><th>Email</th><th>Edad</th><th>Fecha Registro</th><th>Acciones</th></tr>";
foreach ($usuarios as $usuario) {
echo "<tr>";
echo "<td>" . $usuario['id'] . "</td>";
echo "<td>" . htmlspecialchars($usuario['nombre']) . "</td>";
echo "<td>" . htmlspecialchars($usuario['email']) . "</td>";
echo "<td>" . $usuario['edad'] . "</td>";
echo "<td>" . $usuario['fecha_registro'] . "</td>";
echo "<td class='acciones'>";
echo "<a href='editar.php?id=" . $usuario['id'] . "' class='editar'>Editar</a>";
echo "<a href='eliminar.php?id=" . $usuario['id'] . "' class='eliminar' onclick='return confirm(\"¿Seguro que quieres eliminar?\");'>Eliminar</a>";
echo "</td>";
echo "</tr>";
}
echo "</table>";
} else {
echo "<p>No hay usuarios registrados.</p>";
}
} catch(PDOException $e) {
echo "❌ Error: " . $e->getMessage();
}
?>
<p><a href="insertar.php">Agregar nuevo usuario</a></p>
</body>
</html>
💡 ¿Por qué htmlspecialchars()?
Protege contra XSS (Cross-Site Scripting). Si alguien pone código malicioso en el nombre, htmlspecialchars() lo convierte en texto plano inofensivo.
✏️ UPDATE: Editar datos
Crear archivo editar.php:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Editar Usuario</title>
<style>
body { font-family: Arial; margin: 20px; }
form { max-width: 400px; }
input, button { margin: 10px 0; padding: 8px; width: 100%; }
button { background: #28a745; color: white; border: none; }
</style>
</head>
<body>
<h2>Editar Usuario</h2>
<?php
include 'conexion.php';
// ✅ Verificar que existe el ID
if (!isset($_GET['id'])) {
die("❌ ID no especificado");
}
$id = $_GET['id'];
// ✅ Procesar actualización
if ($_POST) {
$nombre = $_POST['nombre'];
$email = $_POST['email'];
$edad = $_POST['edad'];
try {
$sql = "UPDATE usuarios SET nombre = ?, email = ?, edad = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$nombre, $email, $edad, $id]);
echo "<p style='color: green;'>✅ Usuario actualizado correctamente!</p>";
echo "<p><a href='mostrar.php'>Volver a la lista</a></p>";
} catch(PDOException $e) {
echo "<p style='color: red;'>❌ Error: " . $e->getMessage() . "</p>";
}
}
// ✅ Obtener datos actuales del usuario
try {
$sql = "SELECT * FROM usuarios WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);
$usuario = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$usuario) {
die("❌ Usuario no encontrado");
}
} catch(PDOException $e) {
die("❌ Error: " . $e->getMessage());
}
?>
<form method="POST">
<label>Nombre:</label>
<input type="text" name="nombre" value="<?= htmlspecialchars($usuario['nombre']) ?>" required>
<label>Email:</label>
<input type="email" name="email" value="<?= htmlspecialchars($usuario['email']) ?>" required>
<label>Edad:</label>
<input type="number" name="edad" value="<?= $usuario['edad'] ?>" min="1">
<button type="submit">Actualizar Usuario</button>
</form>
<p><a href="mostrar.php">Volver a la lista</a></p>
</body>
</html>
🗑️ DELETE: Eliminar datos
Crear archivo eliminar.php:
<?php
include 'conexion.php';
// ✅ Verificar que existe el ID
if (!isset($_GET['id'])) {
header("Location: mostrar.php?error=id_no_especificado");
exit;
}
$id = $_GET['id'];
try {
// ✅ Verificar que el usuario existe antes de eliminar
$sql_check = "SELECT COUNT(*) FROM usuarios WHERE id = ?";
$stmt_check = $pdo->prepare($sql_check);
$stmt_check->execute([$id]);
if ($stmt_check->fetchColumn() == 0) {
header("Location: mostrar.php?error=usuario_no_existe");
exit;
}
// ✅ Eliminar el usuario
$sql = "DELETE FROM usuarios WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);
// ✅ Redirigir con mensaje de éxito
header("Location: mostrar.php?mensaje=usuario_eliminado");
exit;
} catch(PDOException $e) {
header("Location: mostrar.php?error=database_error");
exit;
}
?>
🏠 Página principal del CRUD
Crear archivo index.php para tener una página principal:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Mi Primer CRUD con PHP y MySQL</title>
<style>
body { font-family: Arial; text-align: center; margin: 50px; }
.menu { display: flex; justify-content: center; gap: 20px; margin-top: 30px; }
.menu a { padding: 15px 25px; background: #007cba; color: white; text-decoration: none; border-radius: 5px; }
.menu a:hover { background: #005a87; }
.stats { margin-top: 40px; background: #f8f9fa; padding: 20px; border-radius: 10px; }
</style>
</head>
<body>
<h1>🎉 Mi Primer CRUD con PHP y MySQL</h1>
<p>Sistema completo para gestionar usuarios</p>
<div class="menu">
<a href="insertar.php">➕ Agregar Usuario</a>
<a href="mostrar.php">👥 Ver Usuarios</a>
</div>
<div class="stats">
<h3>📊 Estadísticas</h3>
<?php
include 'conexion.php';
try {
$sql = "SELECT COUNT(*) as total FROM usuarios";
$stmt = $pdo->query($sql);
$total = $stmt->fetchColumn();
echo "<p>👤 Total de usuarios registrados: <strong>$total</strong></p>";
} catch(PDOException $e) {
echo "<p>Error al obtener estadísticas</p>";
}
?>
</div>
</body>
</html>
🎯 Probando tu aplicación CRUD
Sigue estos pasos para probar que todo funcione:
- Ve a http://localhost/mi_crud/
- Haz clic en "Agregar Usuario"
- Llena el formulario con datos de prueba
- Verifica que aparezca "Usuario agregado correctamente"
- Ve a "Ver Usuarios" - deberías ver tu usuario en la tabla
- Prueba editar el usuario
- Finalmente, prueba eliminar (¡con cuidado!)
🔒 Seguridad implementada
💡 Siguientes pasos
Ahora que dominas CRUD básico, puedes mejorar tu aplicación:
🚀 Mejoras que puedes agregar:
- Paginación: Si tienes muchos usuarios, mostrar de 10 en 10
- Búsqueda: Filtrar usuarios por nombre o email
- Validaciones avanzadas: Verificar que el email sea único
- Subida de imágenes: Avatar para cada usuario
- Sistema de login: Solo usuarios autenticados pueden acceder
- Mejor diseño: CSS con Bootstrap o un framework
⚠️ Importante para producción:
- Variables de entorno: NUNCA credenciales en el código
- HTTPS: Siempre usar conexiones encriptadas
- Usuarios únicos: Nunca usar "root" en producción
- Validaciones: Validar TODOS los datos de entrada
- Autenticación: Sistema de login y permisos
- Rate limiting: Evitar ataques de fuerza bruta
- Logs de seguridad: Registrar intentos de acceso
🤔 ¿Dudas comunes?
P: ¿Por qué usar PDO en lugar de mysqli?
R: PDO funciona con múltiples bases de datos (MySQL, PostgreSQL, SQLite) y tiene sintaxis más limpia.
P: ¿Es seguro este código?
R: Para aprendizaje, sí. Para producción necesitas más validaciones, HTTPS, y manejo de sesiones.
P: ¿Puedo usar este código en mi proyecto?
R: ¡Por supuesto! Es tuyo. Solo recuerda añadir las mejoras de seguridad mencionadas.
🎯 ¿Listo para el siguiente nivel?
Ya dominas CRUD con PHP y MySQL. Es hora de crear aplicaciones más avanzadas con autenticación y APIs.
👉 PHP Sessions y Login: Tu primer sistema de autenticaciónAprende a crear un sistema de login completo con sesiones, cookies y seguridad profesional