Laravel Models: A Comprehensive Guide
19 May 2025 | Category: Laravel
In Laravel, Eloquent ORM (Object-Relational Mapping) models are used to interact with database tables, providing an intuitive, object-oriented way to perform CRUD (Create, Read, Update, Delete) operations. Each model represents a database table and allows you to query and manipulate data using PHP objects. This SEO-friendly, plagiarism-free guide explains how to create, configure, and use Eloquent models in Laravel, including relationships, mass assignment, and common operations. Based on Laravel 11 (as of May 19, 2025), this tutorial builds on previous discussions about migrations and is designed for beginners and intermediate developers.
What is a Laravel Model?
A Laravel model is a PHP class that extends Illuminate\Database\Eloquent\Model
and maps to a database table. It provides methods to query the table, define relationships with other tables, and handle data operations. Eloquent models simplify database interactions by abstracting raw SQL into fluent, object-oriented syntax.
Key Features of Eloquent Models
- Active Record Pattern: Each model instance represents a table row, with attributes matching column values.
- Relationships: Define one-to-one, one-to-many, or many-to-many relationships.
- Mass Assignment: Safely insert or update multiple fields.
- Timestamps: Automatically manage
created_at
andupdated_at
. - Soft Deletes: Support soft deletion with a
deleted_at
column. - Query Builder: Chain methods to build complex queries.
Creating a Model
Laravel’s Artisan CLI simplifies model creation. Models are typically stored in the app/Models/
directory (since Laravel 8).
Basic Model Creation
To create a model, use the make:model
command:
php artisan make:model Post
This generates app/Models/Post.php
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
//
}
Model with Migration, Factory, or Seeder
You can generate related resources with flags:
php artisan make:model Post -mfs
-m
: Creates a migration file (database/migrations/2025_05_19_123456_create_posts_table.php
).-f
: Creates a factory (database/factories/PostFactory.php
).-s
: Creates a seeder (database/seeders/PostSeeder.php
).
Example Migration (from previous discussions):
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title', 255)->index();
$table->string('slug')->unique();
$table->text('content');
$table->boolean('is_published')->default(false);
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
$table->softDeletes();
});
}
Configuring a Model
Eloquent models are highly customizable. Below are key properties and methods to configure a model.
1. Table Name
By default, Laravel assumes the table name is the plural, snake_case form of the model name (e.g., Post
→ posts
). Override this if needed:
class Post extends Model
{
protected $table = 'blog_posts';
}
2. Primary Key
The default primary key is id
(auto-incrementing). Customize it:
protected $primaryKey = 'post_id';
3. Disabling Timestamps
If the table doesn’t have created_at
or updated_at
:
public $timestamps = false;
4. Mass Assignment
To prevent mass-assignment vulnerabilities, specify which columns are fillable:
protected $fillable = [
'title',
'slug',
'content',
'is_published',
'user_id',
];
Alternatively, use $guarded
to specify non-fillable columns:
protected $guarded = ['id'];
5. Soft Deletes
Enable soft deletes for the model if the table has a deleted_at
column:
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
protected $fillable = ['title', 'slug', 'content', 'is_published', 'user_id'];
}
- Records are marked as deleted (with
deleted_at
) instead of being removed. - Use
withTrashed()
oronlyTrashed()
to query soft-deleted records.
6. Date Fields
Specify columns that should be treated as dates (automatically cast to Carbon
instances):
protected $dates = ['published_at', 'deleted_at'];
7. Attribute Casting
Cast column values to specific types:
protected $casts = [
'is_published' => 'boolean',
'published_at' => 'datetime',
'metadata' => 'array', // For JSON columns
];
Example Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
protected $fillable = [
'title',
'slug',
'content',
'is_published',
'user_id',
];
protected $casts = [
'is_published' => 'boolean',
'published_at' => 'datetime',
];
protected $dates = ['published_at', 'deleted_at'];
}
Basic CRUD Operations
Eloquent models make CRUD operations straightforward.
1. Creating Records
Use create()
for mass assignment or save()
for individual attributes:
// Mass assignment
$post = Post::create([
'title' => 'My First Post',
'slug' => 'my-first-post',
'content' => 'This is the content.',
'is_published' => true,
'user_id' => 1,
]);
// Individual attributes
$post = new Post;
$post->title = 'Another Post';
$post->slug = 'another-post';
$post->content = 'More content.';
$post->is_published = false;
$post->user_id = 1;
$post->save();
2. Reading Records
Retrieve records using query methods:
// Get all posts
$posts = Post::all();
// Find by ID
$post = Post::find(1);
// Find or fail (throws ModelNotFoundException if not found)
$post = Post::findOrFail(1);
// Query with conditions
$publishedPosts = Post::where('is_published', true)->get();
// First matching record
$firstPost = Post::where('user_id', 1)->first();
3. Updating Records
Update existing records:
// Update by ID
$post = Post::find(1);
$post->title = 'Updated Title';
$post->save();
// Mass update
Post::where('user_id', 1)->update(['is_published' => false]);
4. Deleting Records
Delete records (or soft delete if enabled):
// Delete a single record
$post = Post::find(1);
$post->delete();
// Delete by query
Post::where('is_published', false)->delete();
// Restore soft-deleted record
Post::withTrashed()->find(1)->restore();
// Permanently delete
Post::withTrashed()->find(1)->forceDelete();
Defining Relationships
Eloquent supports relationships to connect models (e.g., a Post
belongs to a User
). Define relationships as methods in the model.
1. One-to-One
A Post
has one Meta
(e.g., metadata stored in a separate table).
class Post extends Model
{
public function meta()
{
return $this->hasOne(Meta::class);
}
}
Usage:
$meta = Post::find(1)->meta;
2. One-to-Many
A User
has many Posts
.
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
Inverse (a Post
belongs to a User
):
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Usage:
$posts = User::find(1)->posts;
$user = Post::find(1)->user;
3. Many-to-Many
A Post
can have many Tags
, and a Tag
can belong to many Posts
(via a pivot table, e.g., post_tag
).
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
Usage:
$tags = Post::find(1)->tags;
Post::find(1)->tags()->attach([1, 2]); // Attach tags
Post::find(1)->tags()->detach(1); // Detach a tag
Querying with Eloquent
Eloquent provides a fluent query builder for advanced queries.
Examples
// Published posts
$posts = Post::where('is_published', true)->orderBy('title')->get();
// Posts by user
$posts = Post::where('user_id', 1)->paginate(10);
// Eager load relationships
$posts = Post::with('user')->get();
// Count published posts
$count = Post::where('is_published', true)->count();
// Soft-deleted posts
$trashed = Post::onlyTrashed()->get();
Best Practices for Models
- Use Fillable or Guarded:
- Protect against mass-assignment vulnerabilities with
$fillable
or$guarded
.
- Define Relationships:
- Use methods like
hasMany
orbelongsTo
for clear relationships.
- Leverage Soft Deletes:
- Enable soft deletes for recoverable data.
- Cast Attributes:
- Use
$casts
for proper type handling (e.g., booleans, dates).
- Keep Models Focused:
- Avoid complex logic in models; use repositories or services for business logic.
- Eager Load Relationships:
- Use
with()
to prevent N+1 query issues:php $posts = Post::with('user')->get();
- Use Factories for Testing:
- Generate test data with factories (e.g.,
Post::factory()->create()
).
- Follow Naming Conventions:
- Model: Singular, PascalCase (e.g.,
Post
). - Table: Plural, snake_case (e.g.,
posts
).
- Document Relationships:
- Add PHPDoc comments for clarity: “`php /**
- Get the user who created the post.
*/
public function user()
{
return $this->belongsTo(User::class);
}
“`
- Get the user who created the post.
Debugging Models
- Check Table Name:
- Verify
$table
or default naming (pluralized model name). - Mass Assignment Errors:
- Ensure columns are in
$fillable
or$guarded
is configured correctly. - Relationship Issues:
- Confirm foreign key columns exist (e.g.,
user_id
forbelongsTo
). - Use
with()
to debug eager loading. - Query Debugging:
- Use
toSql()
to inspect queries:php echo Post::where('is_published', true)->toSql();
- Enable query logging:
php DB::enableQueryLog(); Post::all(); dd(DB::getQueryLog());
- Logs:
- Check
storage/logs/laravel.log
for errors.
Example: Complete Model Workflow
Step 1: Create Model and Migration
php artisan make:model Post -m
Migration (database/migrations/2025_05_19_123456_create_posts_table.php
):
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title', 255)->index();
$table->string('slug')->unique();
$table->text('content');
$table->boolean('is_published')->default(false);
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
$table->softDeletes();
});
}
Model (app/Models/Post.php
):
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
protected $fillable = [
'title',
'slug',
'content',
'is_published',
'user_id',
];
protected $casts = [
'is_published' => 'boolean',
];
/**
* Get the user who created the post.
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
Step 2: Run Migration
php artisan migrate
Step 3: CRUD Operations
Controller (app/Http/Controllers/PostController.php
):
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
$posts = Post::with('user')->where('is_published', true)->get();
return view('posts.index', compact('posts'));
}
public function store(Request $request)
{
$post = Post::create($request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|string|unique:posts',
'content' => 'required|string',
'is_published' => 'boolean',
'user_id' => 'required|exists:users,id',
]));
return redirect()->route('posts.index');
}
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
public function update(Request $request, Post $post)
{
$post->update($request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|string|unique:posts,slug,' . $post->id,
'content' => 'required|string',
'is_published' => 'boolean',
]));
return redirect()->route('posts.index');
}
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index');
}
}
Step 4: Blade View
View (resources/views/posts/index.blade.php
):
@extends('layouts.app')
@section('content')
<h1>Posts</h1>
@forelse ($posts as $post)
<article>
<h2>{{ $post->title }}</h2>
<p>By {{ $post->user->name }}</p>
<p>{{ $post->content }}</p>
</article>
@empty
<p>No posts found.</p>
@endforelse
@endsection
Conclusion
Laravel’s Eloquent models provide a powerful and elegant way to interact with your database, abstracting complex SQL into simple PHP methods. By configuring models with $fillable
, relationships, and casts, and using them for CRUD operations, you can build robust applications efficiently. Combining models with migrations, controllers, and Blade views creates a seamless workflow for data management.
Next Steps:
- Create a model with a migration:
php artisan make:model ModelName -m
. - Define relationships and perform CRUD operations.
- Experiment with factories and seeders for test data.
For deeper insights, explore Laravel’s official documentation or connect with the Laravel community on platforms like X. Start building with Eloquent models today!
Laravel Model Example
This artifact provides a practical example of an Eloquent model for a posts
table, including migration, model, controller, and Blade view.
Migration (database/migrations/2025_05_19_123456_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', 255)->index();
$table->string('slug')->unique();
$table->text('content');
$table->boolean('is_published')->default(false);
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Model (app/Models/Post.php
)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
protected $fillable = [
'title',
'slug',
'content',
'is_published',
'user_id',
];
protected $casts = [
'is_published' => 'boolean',
];
/**
* Get the user who created the post.
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
Controller (app/Http/Controllers/PostController.php
)
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
$posts = Post::with('user')->where('is_published', true)->get();
return view('posts.index', compact('posts'));
}
public function store(Request $request)
{
$post = Post::create($request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|string|unique:posts',
'content' => 'required|string',
'is_published' => 'boolean',
'user_id' => 'required|exists:users,id',
]));
return redirect()->route('posts.index');
}
}
Blade View (resources/views/posts/index.blade.php
)
@extends('layouts.app')
@section('content')
<h1>Posts</h1>
@forelse ($posts as $post)
<article>
<h2>{{ $post->title }}</h2>
<p>By {{ $post->user->name }}</p>
<p>{{ $post->content }}</p>
</article>
@empty
<p>No posts found.</p>
@endforelse
@endsection
Usage
- Ensure your
.env
file is configured and theusers
table exists. - Save the migration, model, controller, and Blade view.
- Run the migration:
php artisan migrate
- Define routes in
routes/web.php
:
use App\Http\Controllers\PostController;
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
- Test by accessing
/posts
or creating a post via a form.
This example demonstrates an Eloquent model with mass assignment, soft deletes, a relationship, and basic CRUD operations, integrated with a controller and Blade view.