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

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.