Laravel

Aprende Laravel: Models, Database & Eloquent

Autorangel cruz
Publicado
Lectura5 min de lectura
Actualizado
Aprende Laravel: Models, Database & Eloquent

Laravel 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 de SELECT * 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:

  1. Model: app/Models/Post.php
  2. 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_at y updated_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') - TEXT
  • integer('views') - INTEGER
  • boolean('published') - BOOLEAN
  • timestamp('published_at')->nullable() - TIMESTAMP NULL
  • timestamps() - Crea created_at y updated_at
  • foreignId('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=1 en 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

Bio
Angel Cruz

Desarrollador web full-stack enfocado en React, buenas prácticas y código abierto. Apasionado por construir productos útiles y compartir lo aprendido en el camino.