En los posts anteriores aprendiste a crear rutas, vistas y controllers. Ahora es momento de trabajar con la base de datos usando el poderoso Eloquent ORM.
¿Qué es Eloquent ORM?
ORM significa Object-Relational Mapping (Mapeo Objeto-Relacional). Eloquent es el ORM de Laravel que te permite trabajar con la base de datos usando objetos PHP en lugar de escribir SQL directamente.
Beneficios de usar Eloquent:
- Sintaxis como
Post::all()en lugar deSELECT * FROM posts - Tu IDE puede autocompletar propiedades
- Define relaciones entre tablas fácilmente
- Previene SQL injection automáticamente
- Versiona cambios en tu base de datos con Migrations
El patrón Active Record
Eloquent usa el patrón Active Record, donde cada model representa una tabla y cada instancia del model representa una fila:
// Una instancia = una fila en la tabla
$post = new Post();
$post->title = 'Mi primer post';
$post->save(); // INSERT INTO posts...
Creando tu Primer Model
Usa el comando Artisan make:model. El flag -m crea automáticamente una migration:
php artisan make:model Post -m
Esto crea dos archivos:
- Model:
app/Models/Post.php - Migration:
database/migrations/xxxx_create_posts_table.php
Convenciones de naming:
- Model:
Post(singular, PascalCase) - Tabla asociada:
posts(plural, snake_case) - Primary key:
id(autoincremento por defecto) - Timestamps:
created_atyupdated_at(automáticos)
Convención sobre configuración: Si sigues las convenciones de Laravel, no necesitas configurar casi nada. El model Post automáticamente usa la tabla posts.
Migrations: Construyendo tu Schema
Las migrations son como “control de versiones” para tu base de datos. Cada migration define cómo crear o modificar tablas.
Abre la migration generada (database/migrations/xxxx_create_posts_table.php):
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->boolean('published')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Column types comunes:
id()- Primary key autoincremento (BIGINT UNSIGNED)string('name', 100)- VARCHAR(100)text('content')- TEXTinteger('views')- INTEGERboolean('published')- BOOLEANtimestamp('published_at')->nullable()- TIMESTAMP NULLtimestamps()- Crea created_at y updated_atforeignId('user_id')->constrained()- Foreign key a tabla users
Ejecutando Migrations
Para crear las tablas en tu base de datos:
php artisan migrate
Verás algo como:
INFO Running migrations.
2025_02_14_000001_create_posts_table ............ 10ms DONE
Otros comandos útiles:
php artisan migrate:rollback # Deshace el último batch
php artisan migrate:fresh # Borra todas las tablas y re-crea
php artisan migrate:refresh # Rollback + migrate
Nunca modifiques migrations ya ejecutadas en producción. Siempre crea una nueva migration para cambios.
CRUD con Eloquent
Ahora que tienes tu tabla, puedes interactuar con ella usando el model.
Create - Crear registros:
// Opción 1: Create + Save
$post = new Post();
$post->title = 'Mi Primer Post';
$post->content = 'Contenido del post...';
$post->save();
// Opción 2: Create (mass assignment)
$post = Post::create([
'title' => 'Mi Primer Post',
'content' => 'Contenido del post...',
]);
// Opción 3: firstOrCreate (busca o crea)
$post = Post::firstOrCreate(
['title' => 'Mi Primer Post'],
['content' => 'Contenido...']
);
Read - Leer registros:
// Todos los posts
$posts = Post::all();
// Buscar por ID
$post = Post::find(1);
// Buscar por ID o lanzar 404
$post = Post::findOrFail(1);
// Primera coincidencia
$post = Post::where('published', true)->first();
// Múltiples condiciones
$posts = Post::where('published', true)
->where('views', '>', 100)
->get();
// Ordenar
$posts = Post::orderBy('created_at', 'desc')->get();
// Limitar resultados
$posts = Post::take(10)->get();
// Paginación
$posts = Post::paginate(15);
Update - Actualizar registros:
// Opción 1: Find + Update
$post = Post::find(1);
$post->title = 'Título Actualizado';
$post->save();
// Opción 2: Update directo
$post = Post::find(1);
$post->update([
'title' => 'Título Actualizado',
'published' => true,
]);
// Opción 3: Update masivo (sin instanciar)
Post::where('published', false)
->update(['published' => true]);
Delete - Eliminar registros:
// Opción 1: Find + Delete
$post = Post::find(1);
$post->delete();
// Opción 2: Delete directo por ID
Post::destroy(1);
// Opción 3: Delete múltiples
Post::destroy([1, 2, 3]);
// Opción 4: Delete condicional
Post::where('views', '<', 10)->delete();
Mass Assignment Protection
Por seguridad, Laravel protege contra mass assignment (asignación masiva). Debes especificar qué campos son “llenables”:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = [
'title',
'content',
'published',
];
}
O especificar qué campos NO son llenables:
protected $guarded = ['id', 'user_id'];
¿Por qué mass assignment protection? Imagina un usuario malicioso enviando
is_admin=1en un formulario. Sin protección, podría hacerse admin!
Relationships: Conectando Models
Una de las features más poderosas de Eloquent son las relaciones.
Ejemplo: Un User tiene muchos Posts (One-to-Many)
1. Agrega foreign key en la migration:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('content');
$table->timestamps();
});
2. Define la relación en el Model User:
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
3. Define la relación inversa en Post:
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
4. Usa las relaciones:
// Obtener posts de un usuario
$user = User::find(1);
$posts = $user->posts; // Collection de Posts
// Obtener el autor de un post
$post = Post::find(1);
$author = $post->user; // User model
El Problema N+1 y Eager Loading
Sin eager loading, Eloquent ejecuta 1 query por cada relación:
// ❌ N+1 Problem - 1 query + N queries
$posts = Post::all(); // 1 query
foreach ($posts as $post) {
echo $post->user->name; // 1 query por post!
}
// Total: 1 + 10 = 11 queries para 10 posts
Solución: usa with() para eager loading:
// ✅ Eager Loading - Solo 2 queries
$posts = Post::with('user')->get(); // 2 queries total
foreach ($posts as $post) {
echo $post->user->name; // Sin queries adicionales
}
El problema N+1 es una de las causas principales de lentitud en aplicaciones Laravel. Siempre usa eager loading cuando accedas a relaciones en loops.
UUIDv7 en Laravel 13
Novedad en Laravel 13: Ahora puedes usar UUIDs versión 7 como primary keys. Los UUIDv7 son time-ordered (ordenados por tiempo), lo que mejora el performance en bases de datos.
use Illuminate\Database\Eloquent\Concerns\HasUuids;
class Post extends Model
{
use HasUuids; // Usa UUIDv7 automáticamente
// No necesitas definir $keyType, Laravel lo detecta
}
Y en tu migration:
Schema::create('posts', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('title');
// ...
});
Casting: Transformación Automática
Eloquent puede convertir automáticamente tipos de datos:
class Post extends Model
{
protected $casts = [
'published_at' => 'datetime', // String → Carbon instance
'settings' => 'array', // JSON → PHP array
'published' => 'boolean', // 0/1 → true/false
'views' => 'integer',
];
}
Ahora puedes usar:
$post->published_at->format('Y-m-d'); // Carbon methods
$post->settings['theme']; // Array access
Accessors & Mutators (Breve)
Desde Laravel 9 existe una sintaxis moderna usando Attribute::make(). En Laravel 13 es la forma recomendada:
use Illuminate\Database\Eloquent\Casts\Attribute;
// Accessor + Mutator combinados en un método
protected function title(): Attribute
{
return Attribute::make(
get: fn (string $value) => strtoupper($value),
set: fn (string $value) => trim(strip_tags($value)),
);
}
echo $post->title; // "MI PRIMER POST"
$post->title = '<h1> Mi Post </h1>'; // Guarda "Mi Post"
La sintaxis antigua (getTitleAttribute / setTitleAttribute) sigue funcionando en Laravel 13 pero es considerada legacy:
// Forma legacy (todavía válida pero no recomendada)
public function getTitleAttribute($value)
{
return strtoupper($value);
}
Siguiente Paso
Ahora que dominas Models, Migrations y Eloquent, es momento de poner todo en práctica. En el próximo post crearemos un Blog completo desde cero, usando todo lo aprendido: rutas, controllers, vistas, models y relaciones.
Preguntas Frecuentes
¿Qué es Eloquent ORM?
Eloquent es el ORM (Object-Relational Mapping) de Laravel que te permite trabajar con la base de datos usando objetos PHP en lugar de escribir SQL directamente. Por ejemplo: Post::all() en lugar de SELECT * FROM posts. Previene SQL injection automáticamente y hace el código más legible.
¿Cómo creo un Model en Laravel?
Usa php artisan make:model Post -m. El flag -m crea automáticamente una migration asociada. El model Post (singular, PascalCase) se asocia automáticamente con la tabla posts (plural, snake_case) por convención.
¿Qué son las Migrations?
Las migrations son como “control de versiones” para tu base de datos. Cada migration define cómo crear o modificar tablas. Ejecutas php artisan migrate para aplicar los cambios. Nunca modifiques migrations ya ejecutadas en producción - siempre crea una nueva migration para cambios.
¿Qué es Mass Assignment Protection?
Es una protección de seguridad contra asignación masiva. Debes especificar qué campos son “llenables” con protected $fillable = ['title', 'content'] o qué NO son llenables con protected $guarded = ['id']. Esto previene que usuarios maliciosos envíen campos como is_admin=1 en formularios.
¿Qué es el problema N+1 y cómo se soluciona?
El problema N+1 ocurre cuando haces 1 query + N queries adicionales en un loop. Ejemplo: Post::all() (1 query) + $post->user->name dentro del loop (N queries). Solución: Usar eager loading con Post::with('user')->get() - solo 2 queries total en lugar de N+1.
¿Qué son los UUIDs en Laravel 13?
Laravel 13 soporta UUIDs versión 7 como primary keys. Los UUIDv7 son time-ordered (ordenados por tiempo), lo que mejora el performance en bases de datos. Usa use HasUuids; en tu model y $table->uuid('id')->primary() en la migration.
Recursos Adicionales
Video de la lección
Ver video tutorial: Aprende Laravel - Models y Database
Playlist completa en YouTube: Aprende Laravel @ YouTube
