Laravel Authorization with Gates and Policies: Controlling User Access
Manage user permissions and access control.
Eloquent is Laravel's built-in Object-Relational Mapper (ORM) that provides a beautiful, simple ActiveRecord implementation for working with your database. It allows you to interact with your database tables using intuitive, object-oriented syntax.
In simple terms: Eloquent lets you work with database records as if they were regular PHP objects.
// ❌ Traditional PHP with raw SQL
$users = [];
$result = mysqli_query($connection, "SELECT * FROM users WHERE active = 1");
while ($row = mysqli_fetch_assoc($result)) {
$users[] = $row;
}
// ✅ Laravel Eloquent
$users = User::where('active', true)->get();
// Instead of complex SQL...
"SELECT * FROM posts
WHERE published_at <= NOW()
AND (category_id = 1 OR author_id = 5)
ORDER BY created_at DESC
LIMIT 10"
// You write clean PHP...
$posts = Post::where('published_at', '<=', now())
->where(function($query) {
$query->where('category_id', 1)
->orWhere('author_id', 5);
})
->latest()
->limit(10)
->get();
The same Eloquent code works across MySQL, PostgreSQL, SQLite, and SQL Server.
Eloquent automatically uses parameter binding to prevent SQL injection attacks.
Relationships, eager loading, mutators, accessors, scopes, and much more!
<?php
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// That's it! Eloquent handles the rest automatically
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
// Custom table name
protected $table = 'blog_posts';
// Custom primary key
protected $primaryKey = 'post_id';
// Non-incrementing primary key
public $incrementing = false;
// Custom key type
protected $keyType = 'string';
// Disable timestamps
public $timestamps = false;
// Custom timestamp formats
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'last_updated';
// Custom database connection
protected $connection = 'mysql_blog';
// Default attributes
protected $attributes = [
'views' => 0,
'is_published' => false,
];
}
// Method 1: Create and save
$user = new User;
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->save();
// Method 2: Create with array
$user = new User([
'name' => 'Jane Doe',
'email' => 'jane@example.com'
]);
$user->save();
// Method 3: Create in one line
$user = User::create([
'name' => 'Mike Smith',
'email' => 'mike@example.com'
]);
// Method 4: First or create (prevents duplicates)
$user = User::firstOrCreate(
['email' => 'john@example.com'], // Check if exists
['name' => 'John Doe'] // Create with these attributes
);
// Method 5: First or new
$user = User::firstOrNew(
['email' => 'sarah@example.com'],
['name' => 'Sarah Wilson']
);
$user->save(); // Only saves if it's new
// Get all records
$users = User::all();
// Find by primary key
$user = User::find(1);
// Find or fail (throws ModelNotFoundException)
$user = User::findOrFail(999); // Throws exception if not found
// Get first record
$user = User::first();
// Get specific columns
$users = User::select('name', 'email')->get();
// Chunk results for large datasets
User::chunk(200, function ($users) {
foreach ($users as $user) {
// Process 200 users at a time
}
});
// Lazy loading for memory efficiency
foreach (User::lazy() as $user) {
// Processes one user at a time in memory
}
// Cursor (even more memory efficient)
foreach (User::cursor() as $user) {
// Uses PHP generators
}
// Method 1: Retrieve, update, save
$user = User::find(1);
$user->name = 'Updated Name';
$user->save();
// Method 2: Mass update
User::where('active', false)
->update(['active' => true]);
// Method 3: Update or create
User::updateOrCreate(
['email' => 'john@example.com'], // Find by these attributes
['name' => 'John Updated'] // Update with these attributes
);
// Method 4: Increment/Decrement
$post = Post::find(1);
$post->increment('views');
$post->increment('votes', 5);
$post->decrement('credits', 10);
// Method 1: Retrieve and delete
$user = User::find(1);
$user->delete();
// Method 2: Direct delete
User::where('active', false)->delete();
// Method 3: Destroy by primary key
User::destroy(1);
User::destroy([1, 2, 3]);
User::destroy(1, 2, 3);
// Method 4: Soft delete (if enabled)
$user->delete(); // Sets deleted_at timestamp
// Method 5: Force delete (ignores soft delete)
$user->forceDelete();
// Method 6: Delete all models (truncate)
User::truncate();
// Simple where
$users = User::where('votes', '>', 100)->get();
// Multiple conditions
$users = User::where('votes', '>', 100)
->where('name', 'John')
->get();
// OR conditions
$users = User::where('votes', '>', 100)
->orWhere('name', 'John')
->get();
// Array of conditions
$users = User::where([
['status', '=', 'active'],
['votes', '>=', 100]
])->get();
// Additional where methods
$users = User::whereIn('category_id', [1, 2, 3])->get();
$users = User::whereNotIn('category_id', [4, 5, 6])->get();
$users = User::whereNull('updated_at')->get();
$users = User::whereNotNull('updated_at')->get();
$users = User::whereBetween('votes', [1, 100])->get();
$users = User::whereNotBetween('votes', [101, 200])->get();
$users = User::whereDate('created_at', '2023-12-25')->get();
$users = User::whereMonth('created_at', '12')->get();
$users = User::whereDay('created_at', '25')->get();
$users = User::whereYear('created_at', '2023')->get();
$users = User::whereTime('created_at', '=', '14:30:00')->get();
$users = User::whereColumn('updated_at', '>', 'created_at')->get();
// Ordering
$users = User::orderBy('name', 'desc')->get();
$users = User::latest()->get(); // Order by created_at desc
$users = User::oldest()->get(); // Order by created_at asc
$users = User::inRandomOrder()->get();
// Grouping and Having
$users = User::groupBy('account_id')
->having('account_id', '>', 100)
->get();
// Limits and Offsets
$users = User::limit(10)->get();
$users = User::offset(10)->limit(5)->get(); // Skip 10, take 5
$users = User::take(5)->get(); // Alias for limit
// Distinct results
$users = User::distinct()->get();
// Count
$count = User::count();
$count = User::where('votes', '>', 100)->count();
// Other aggregates
$max = User::max('votes');
$min = User::min('votes');
$avg = User::avg('votes');
$sum = User::sum('votes');
// Check if record exists
if (User::where('email', $email)->exists()) {
// Email exists
}
// Check if no records exist
if (User::where('email', $email)->doesntExist()) {
// Email doesn't exist
}
// User has one Profile
class User extends Model
{
public function profile()
{
return $this->hasOne(Profile::class);
}
}
class Profile extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
// Usage
$user = User::find(1);
$profile = $user->profile; // Get user's profile
$user = $profile->user; // Get profile's user
// User has many Posts
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
// Usage
$user = User::find(1);
foreach ($user->posts as $post) {
echo $post->title;
}
// Create post for user
$user->posts()->create([
'title' => 'New Post',
'content' => 'Post content...'
]);
// Post belongs to many Tags
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
// Usage
$post = Post::find(1);
foreach ($post->tags as $tag) {
echo $tag->name;
}
// Attach/detach tags
$post->tags()->attach($tagId);
$post->tags()->attach([1, 2, 3]);
$post->tags()->detach(1);
$post->tags()->sync([1, 2, 3]); // Only these tags will remain
// Has Many Through
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
}
}
// Polymorphic Relations
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
// ❌ BAD: N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // Makes a new query for each post
}
// ✅ GOOD: Eager loading (2 queries total)
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name; // No additional queries
}
// Load multiple relationships
$posts = Post::with(['user', 'tags', 'comments'])->get();
// Nested eager loading
$posts = Post::with('user.profile')->get();
// Eager load with constraints
$posts = Post::with(['comments' => function ($query) {
$query->where('approved', true);
}])->get();
// Lazy eager loading
$posts = Post::all();
$posts->load('user', 'tags');
// Conditional relationships
$posts = Post::with(['user', 'tags' => function ($query) {
$query->where('active', true);
}])->get();
class User extends Model
{
// Accessor: Get formatted value
public function getNameAttribute($value)
{
return ucwords($value);
}
// Mutator: Set formatted value before save
public function setNameAttribute($value)
{
$this->attributes['name'] = strtolower($value);
}
// Virtual attribute (doesn't exist in database)
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}
// Usage
$user = User::find(1);
echo $user->name; // Automatically formatted
echo $user->full_name; // Virtual attribute
$user->name = 'JOHN DOE'; // Automatically converted to lowercase
$user->save();
class Post extends Model
{
protected $casts = [
'is_published' => 'boolean',
'metadata' => 'array',
'published_at' => 'datetime',
'views' => 'integer',
'rating' => 'decimal:2',
'options' => 'object',
];
}
// Usage
$post = Post::find(1);
if ($post->is_published) { // Automatically cast to boolean
// ...
}
$post->metadata = ['key' => 'value']; // Automatically serialized to JSON
$post->save();
class Post extends Model
{
// Local scope
public function scopePublished($query)
{
return $query->where('is_published', true);
}
public function scopePopular($query, $minViews = 1000)
{
return $query->where('views', '>=', $minViews);
}
// Dynamic scope
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
// Global scope (applied to all queries)
protected static function booted()
{
static::addGlobalScope('active', function ($query) {
$query->where('is_active', true);
});
}
}
// Usage
$posts = Post::published()->get();
$posts = Post::popular(5000)->get();
$posts = Post::ofType('blog')->get();
class Post extends Model
{
protected static function booted()
{
static::creating(function ($post) {
$post->slug = Str::slug($post->title);
});
static::created(function ($post) {
// Send notification, update cache, etc.
});
static::updating(function ($post) {
$post->updated_by = auth()->id();
});
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'title', 'slug', 'content', 'excerpt', 'featured_image',
'is_published', 'published_at', 'user_id', 'category_id'
];
protected $casts = [
'is_published' => 'boolean',
'published_at' => 'datetime',
'metadata' => 'array',
];
protected $attributes = [
'views' => 0,
'is_published' => false,
];
// Relationships
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
// Scopes
public function scopePublished($query)
{
return $query->where('is_published', true)
->where('published_at', '<=', now());
}
public function scopePopular($query, $minViews = 1000)
{
return $query->where('views', '>=', $minViews);
}
public function scopeByCategory($query, $categoryId)
{
return $query->where('category_id', $categoryId);
}
// Accessors
public function getReadingTimeAttribute()
{
$wordCount = str_word_count(strip_tags($this->content));
return ceil($wordCount / 200); // 200 words per minute
}
public function getExcerptAttribute($value)
{
return $value ?: Str::limit(strip_tags($this->content), 150);
}
// Mutators
public function setTitleAttribute($value)
{
$this->attributes['title'] = $value;
$this->attributes['slug'] = Str::slug($value);
}
// Business logic
public function publish()
{
$this->update([
'is_published' => true,
'published_at' => now(),
]);
}
public function unpublish()
{
$this->update([
'is_published' => false,
'published_at' => null,
]);
}
public function incrementViews()
{
$this->increment('views');
}
}
// Usage examples
$publishedPosts = Post::published()->with(['user', 'category', 'tags'])->get();
$popularPosts = Post::published()
->popular(5000)
->orderBy('views', 'desc')
->take(10)
->get();
$post = Post::create([
'title' => 'My New Post',
'content' => 'Post content here...',
'category_id' => 1,
'user_id' => auth()->id(),
]);
$post->tags()->attach([1, 2, 3]);
$post->publish();
Eloquent is Laravel's ActiveRecord ORM implementation that provides an intuitive, object-oriented interface for database interactions, allowing you to work with database records as PHP objects.
find() returns null if the record doesn't exist, while findOrFail() throws a ModelNotFoundException which can be caught and handled appropriately.
Eager loading solves the N+1 query problem by loading related models in a single query instead of making separate queries for each parent model, significantly improving performance.
Accessors format attribute values when retrieving them from models, while mutators format attribute values before saving them to the database.
save() is used on existing model instances, while create() is a static method that both instantiates the model and saves it to the database in one step.
Relationships are defined as methods in model classes that return relationship type objects like hasOne, hasMany, belongsTo, belongsToMany, etc.
// ❌ Avoid N+1 problems
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // Makes new query for each post
}
// ✅ Use eager loading
$posts = Post::with('user')->get();
// ❌ Don't select all columns
$users = User::get();
// ✅ Select only needed columns
$users = User::select('id', 'name', 'email')->get();
// ❌ Avoid loading large datasets into memory
$users = User::all(); // Could be millions of records!
// ✅ Use chunking for large datasets
User::chunk(1000, function ($users) {
foreach ($users as $user) {
// Process in batches
}
});
Now you understand why Eloquent ORM is truly a game-changer for Laravel development! In our next post, we'll explore Laravel Eloquent Models: A Crash Course on Interacting with Your Database to dive deeper into model-specific features and techniques.