Aprende Laravel: Models, Database & Eloquent

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 -mEsto 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 migrateVerás algo como:
INFO Running migrations.
2025_02_14_000001_create_posts_table ............ 10ms DONEOtros 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 + migrateNunca 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 modelEl 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 postsSolució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 accessAccessors & 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
