Complete Guide to Laravel Models
A comprehensive reference for understanding and using Models in Laravel with database integration, controllers, and views
Table of Contents
- 1. Introduction to Laravel Models
- 2. Creating Models
- 3. Migrations
- 4. Eloquent ORM Basics
- 5. Model Relationships
- 6. Factories and Seeders
- 7. Integration with Controllers
- 8. Integration with Views
- 9. Advanced Features
- 10. Best Practices
1. Introduction to Laravel Models
Models in Laravel are the layer that interacts with the database using Eloquent ORM. Each model represents a table in the database and provides a simple and powerful way to interact with data.
Laravel Models allow you to:
- Create, read, update, and delete data (CRUD)
- Define relationships between tables
- Add business logic specific to your data
- Validate data
2. Creating Models
Creating a Simple Model
To create a new model, use the following Artisan command:
php artisan make:model User
This will create a User.php file in the app/Models/ directory:
<!--?php </p> <p>namespace App\Models;</p> <p>use Illuminate\Database\Eloquent\Model;</p> <p>class User extends Model<br ?--> {
//
}
Creating Model with Migration
To create a model with a migration file together:
php artisan make:model Post -m
Creating Model with All Related Files
To create a model with migration, factory, seeder, and controller:
php artisan make:model Product -mcfs
Where:
-m: Migration-c: Controller-f: Factory-s: Seeder
3. Migrations
Migrations manage the database structure and allow you to track changes over time.
Creating a New Migration
php artisan make:migration create_posts_table
Example Posts Table Migration
<!--?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();<br ?--> $table->string('title');
$table->text('content');
$table->string('slug')->unique();
$table->boolean('is_published')->default(false);
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Running Migrations
# Run all pending migrations php artisan migrate # Rollback the last batch of migrations php artisan migrate:rollback # Fresh migrate all migrations php artisan migrate:fresh
4. Eloquent ORM Basics
Basic Model Setup
<!--?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Post extends Model { use SoftDeletes; // Table name (optional if following naming conventions) protected $table = 'posts'; // Primary key protected $primaryKey = 'id'; // Primary key type protected $keyType = 'int'; // Whether primary key is auto-incrementing public $incrementing = true; // Whether to manage timestamps automatically public $timestamps = true; // Mass assignable fields protected $fillable = [ 'title', 'content', 'slug', 'is_published', 'user_id' ]; // Fields protected from mass assignment protected $guarded = [ 'id' ]; // Data type casting protected $casts = [ 'is_published' => 'boolean',<br ?--> 'created_at' => 'datetime',
'updated_at' => 'datetime',
];
// Default field values
protected $attributes = [
'is_published' => false,
];
}
Basic Operations (CRUD)
Creating New Records
// Method 1 $post = new Post(); $post->title = 'Article Title'; $post->content = 'Article Content'; $post->slug = 'article-title'; $post->user_id = 1; $post->save(); // Method 2 - Mass Assignment $post = Post::create([ 'title' => 'Article Title', 'content' => 'Article Content', 'slug' => 'article-title', 'user_id' => 1 ]); // Create or Update $post = Post::updateOrCreate( ['slug' => 'article-title'], [ 'title' => 'Updated Title', 'content' => 'Updated Content' ] );
Retrieving Data
// All records
$posts = Post::all();
// Find by primary key
$post = Post::find(1);
$post = Post::findOrFail(1); // Throws exception if not found
// Find by single condition
$post = Post::where('slug', 'article-title')->first();
$posts = Post::where('is_published', true)->get();
// Find with multiple conditions
$posts = Post::where('is_published', true)
->where('user_id', 1)
->orderBy('created_at', 'desc')
->take(10)
->get();
// Using scopes
$publishedPosts = Post::published()->latest()->get();
Updating Data
// Update single record
$post = Post::find(1);
$post->title = 'New Title';
$post->save();
// Mass update
Post::where('user_id', 1)->update([
'is_published' => true
]);
// Update or create
$post = Post::updateOrCreate(
['slug' => 'new-article'],
['title' => 'New Article', 'content' => 'Article Content']
);
Deleting Data
// Delete single record
$post = Post::find(1);
$post->delete();
// Direct deletion
Post::destroy(1);
Post::destroy([1, 2, 3]);
// Conditional deletion
Post::where('is_published', false)->delete();
// Soft Delete
$post->delete(); // Moves to trash
$post->forceDelete(); // Permanent deletion
$post->restore(); // Restore from trash
5. Model Relationships
One to Many Relationship
// In User model
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
// In Post model
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
// Usage
$user = User::find(1);
$posts = $user->posts; // All user's posts
$post = Post::find(1);
$author = $post->user; // Post author
Many to Many Relationship
// In Post model
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class)
->withTimestamps()
->withPivot('order');
}
}
// In Tag model
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany(Post::class)
->withTimestamps();
}
}
// Usage
$post = Post::find(1);
$tags = $post->tags; // All post tags
// Attach tags
$post->tags()->attach([1, 2, 3]);
$post->tags()->sync([1, 2, 4]); // Removes old and adds new
$post->tags()->detach([2]); // Remove specific tag
Polymorphic Relationship
// In Comment model
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
// In Post model
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
// In Video model
class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
// Usage
$post = Post::find(1);
$comments = $post->comments;
$comment = Comment::find(1);
$parent = $comment->commentable; // Post or Video
6. Factories and Seeders
Creating a Factory
php artisan make:factory PostFactory
<!--?php namespace Database\Factories; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; class PostFactory extends Factory { public function definition(): array { $title = $this->faker->sentence();</p> <p> return [<br ?--> 'title' => $title,
'slug' => Str::slug($title),
'content' => $this->faker->paragraphs(3, true),
'is_published' => $this->faker->boolean(80),
'user_id' => User::factory(),
];
}
// Published state
public function published(): static
{
return $this->state(fn (array $attributes) => [
'is_published' => true,
]);
}
// Draft state
public function draft(): static
{
return $this->state(fn (array $attributes) => [
'is_published' => false,
]);
}
}
Creating a Seeder
php artisan make:seeder PostSeeder
<!--?php namespace Database\Seeders; use App\Models\Post; use App\Models\User; use Illuminate\Database\Seeder; class PostSeeder extends Seeder { public function run(): void { // Create users $users = User::factory(5)->create();</p> <p> // Create posts for each user<br ?--> $users->each(function ($user) {
Post::factory(3)
->published()
->for($user)
->create();
Post::factory(2)
->draft()
->for($user)
->create();
});
}
}
Running Seeders
# Run all seeders php artisan db:seed # Run specific seeder php artisan db:seed --class=PostSeeder # Fresh migrate with seeding php artisan migrate:fresh --seed
7. Integration with Controllers
Creating a Resource Controller
php artisan make:controller PostController --resource
Complete Controller Example
<!--?php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\Request; use Illuminate\Support\Str; class PostController extends Controller { public function index() { $posts = Post::with('user') ->published()<br ?--> ->latest()
->paginate(10);
return view('posts.index', compact('posts'));
}
public function show(Post $post)
{
// Route Model Binding automatically
$post->load('user', 'tags');
return view('posts.show', compact('post'));
}
public function create()
{
return view('posts.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'is_published' => 'boolean'
]);
$validated['slug'] = Str::slug($validated['title']);
$validated['user_id'] = auth()->id();
$post = Post::create($validated);
return redirect()->route('posts.show', $post)
->with('success', 'Post created successfully');
}
public function edit(Post $post)
{
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'is_published' => 'boolean'
]);
if ($validated['title'] !== $post->title) {
$validated['slug'] = Str::slug($validated['title']);
}
$post->update($validated);
return redirect()->route('posts.show', $post)
->with('success', 'Post updated successfully');
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return redirect()->route('posts.index')
->with('success', 'Post deleted successfully');
}
}
8. Integration with Views
Posts Index View
{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')
@section('title', 'All Posts')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>All Posts</h1>
@if($posts->count() > 0)
<div class="row">
@foreach($posts as $post)
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title"><a href="{{ route('posts.show', $post) }}">
{{ $post->title }}
</a></h5>
<p class="card-text">{{ Str::limit($post->content, 150) }}</p>
<div class="d-flex justify-content-between"><small class="text-muted">
By: {{ $post->user->name }}
</small>
<small class="text-muted">
{{ $post->created_at->diffForHumans() }}
</small></div>
</div>
</div>
</div>
@endforeach
</div>
{{ $posts->links() }}
@else
No posts available.
@endif
</div>
</div>
</div>
@endsection
Single Post View
{{-- resources/views/posts/show.blade.php --}}
@extends('layouts.app')
@section('title', $post->title)
@section('content')
<div class="container"><article><header class="mb-4">
<h1>{{ $post->title }}</h1>
<div class="text-muted mb-3">By: {{ $post->user->name }}
<span class="mx-2">|</span>
{{ $post->created_at->format('Y-m-d') }}@if($post->tags->count() > 0)
<div class="mt-2">Tags:
@foreach($post->tags as $tag)
<span class="badge badge-secondary">{{ $tag->name }}</span>
@endforeach</div>
@endif
</div>
</header>
<div class="content">{!! nl2br(e($post->content)) !!}</div>
@can('update', $post)
<div class="mt-4"><a class="btn btn-primary" href="{{ route('posts.edit', $post) }}">
Edit Post
</a></div>
@endcan
</article></div>
@endsection
Create Post Form
{{-- resources/views/posts/create.blade.php --}}
@extends('layouts.app')
@section('title', 'Create New Post')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Create New Post</div>
<div class="card-body"><form action="{{ route('posts.store') }}" method="POST">@csrf
<div class="form-group mb-3"><label for="title">Title</label>
<input id="title" class="form-control @error('title') is-invalid @enderror" name="title" required="" type="text" value="{{ old('title') }}" />
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group mb-3"><label for="content">Content</label>
<textarea id="content" class="form-control @error('content') is-invalid @enderror" name="content" required="" rows="10">{{ old('content') }}</textarea>
@error('content')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-check mb-3"><input id="is_published" class="form-check-input" checked="checked" name="is_published" type="checkbox" value="1" />
<label class="form-check-label" for="is_published">
Publish Post
</label></div>
<button class="btn btn-primary" type="submit">Create Post</button>
<a class="btn btn-secondary" href="{{ route('posts.index') }}">Cancel</a>
</form></div>
</div>
</div>
</div>
</div>
@endsection
9. Advanced Features
Local Scopes
// In Post model
class Post extends Model
{
// Local scope for published posts
public function scopePublished($query)
{
return $query->where('is_published', true);
}
// Local scope for recent posts
public function scopeRecent($query, $days = 7)
{
return $query->where('created_at', '>=', now()->subDays($days));
}
// Local scope for search
public function scopeSearch($query, $term)
{
return $query->where('title', 'like', "%{$term}%")
->orWhere('content', 'like', "%{$term}%");
}
}
// Usage
$posts = Post::published()->recent(30)->get();
$searchResults = Post::search('Laravel')->published()->get();
Accessors and Mutators
// In Post model
class Post extends Model
{
// Accessor to capitalize title
public function getTitleAttribute($value)
{
return ucfirst($value);
}
// Accessor to create post URL
public function getUrlAttribute()
{
return route('posts.show', $this->slug);
}
// Accessor for publication status
public function getStatusAttribute()
{
return $this->is_published ? 'Published' : 'Draft';
}
// Mutator to modify title on save
public function setTitleAttribute($value)
{
$this->attributes['title'] = trim($value);
$this->attributes['slug'] = Str::slug($value);
}
}
// Usage
$post = Post::find(1);
echo $post->title; // Uses accessor
echo $post->url; // Post URL
echo $post->status; // Publication status
$post->title = ' New Title '; // Uses mutator
Model Events
// In Post model
class Post extends Model
{
protected static function booted()
{
// Before creating
static::creating(function ($post) {
if (empty($post->slug)) {
$post->slug = Str::slug($post->title);
}
});
// After creating
static::created(function ($post) {
// Send notification to followers
// Log::info("New post created: {$post->title}");
});
// Before updating
static::updating(function ($post) {
if ($post->isDirty('title')) {
$post->slug = Str::slug($post->title);
}
});
// Before deleting
static::deleting(function ($post) {
// Delete related comments
$post->comments()->delete();
});
}
}
10. Best Practices
Naming and Organization
- Use singular model names in PascalCase (e.g.,
Post,User) - Table names should be plural in snake_case (e.g.,
posts,users) - Organize models in subdirectories by functionality for large projects
Security
// Always use fillable or guarded protected $fillable = ['title', 'content', 'slug']; // Or protected $guarded = ['id', 'created_at', 'updated_at']; // Avoid Mass Assignment vulnerabilities $post = Post::create($request->validated()); // Good $post = Post::create($request->all()); // Dangerous
Performance
// Use eager loading to avoid N+1 queries
$posts = Post::with(['user', 'tags'])->get();
// Use select to load only needed columns
$posts = Post::select(['id', 'title', 'slug'])->get();
// Use pagination for large datasets
$posts = Post::paginate(15);
// Use database transactions for multiple operations
DB::transaction(function () {
$post = Post::create($data);
$post->tags()->sync($tagIds);
});
Code Organization
// Keep models clean - move complex logic to services
class Post extends Model
{
// Simple relationships and basic logic only
public function user()
{
return $this->belongsTo(User::class);
}
public function scopePublished($query)
{
return $query->where('is_published', true);
}
}
// Complex logic in service classes
class PostService
{
public function publishPost(Post $post): bool
{
// Complex publishing logic here
return $post->update(['is_published' => true]);
}
}
Testing Models
// Feature test example
class PostTest extends TestCase
{
use RefreshDatabase;
public function test_can_create_post()
{
$user = User::factory()->create();
$postData = [
'title' => 'Test Post',
'content' => 'Test content',
'user_id' => $user->id
];
$post = Post::create($postData);
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
'slug' => 'test-post'
]);
}
public function test_post_belongs_to_user()
{
$post = Post::factory()->create();
$this->assertInstanceOf(User::class, $post->user);
}
}
Documentation
/**
* Post model representing blog posts
*
* @property int $id
* @property string $title
* @property string $content
* @property string $slug
* @property bool $is_published
* @property int $user_id
* @property Carbon $created_at
* @property Carbon $updated_at
*
* @property-read User $user
* @property-read Collection $tags
* @property-read Collection $comments
*/
class Post extends Model
{
// Model implementation
}
This comprehensive guide covers all aspects of Laravel Models, from basic CRUD operations to advanced features like relationships, factories, and integration with controllers and views. Follow these patterns and best practices to build robust and maintainable Laravel applications.
