⬆️ Go to main menu ➡️ Next (Models Relations)
- Reuse or clone query()
- Remember to use bindings in your raw queries
- Small cheat-sheet for using Full-Text Search with Laravel on MySQL
- Merging eloquent collections
- Perform operation without modifying updated_at field
- You can write transaction-aware code
- Eloquent scopes inside of other relationships
- New
rawValue()
method since Laravel 9.37 - Eloquent where date methods
- Increments and decrements
- No timestamp columns
- Soft-deletes: multiple restore
- Model all: columns
- To Fail or not to Fail
- Column name change
- Map query results
- Change Default Timestamp Fields
- Quick Order by created_at
- Automatic Column Value When Creating Records
- DB Raw Query Calculations Run Faster
- More than One Scope
- No Need to Convert Carbon
- Grouping by First Letter
- Never Update the Column
- Find Many
- Find Many and return specific columns
- Find by Key
- Use UUID instead of auto-increment
- Sub-selects in Laravel Way
- Hide Some Columns
- Exact DB Error
- Soft-Deletes with Query Builder
- Good Old SQL Query
- Use DB Transactions
- Update or Create
- Forget Cache on Save
- Change Format Of Created_at and Updated_at
- Storing Array Type into JSON
- Make a Copy of the Model
- Reduce Memory
- Force query without $fillable/$guarded
- 3-level structure of parent-children
- Perform any action on failure
- Check if record exists or show 404
- Abort if condition failed
- Fill a column automatically while you persist data to the database
- Extra information about the query
- Using the doesntExist() method in Laravel
- Trait that you want to add to a few Models to call their boot() method automatically
- There are two common ways of determining if a table is empty in Laravel
- How to prevent “property of non-object” error
- Get original attributes after mutating an Eloquent record
- A simple way to seed a database
- The crossJoinSub method of the query constructor
- Belongs to Many Pivot table naming
- Order by Pivot Fields
- Find a single record from a database
- Automatic records chunking
- Updating the model without dispatching events
- Periodic cleaning of models from obsolete records
- Immutable dates and casting to them
- The findOrFail method also accepts a list of ids
- Prunable trait to automatically remove models from your database
- withAggregate method
- Date convention
- Eloquent multiple upserts
- Retrieve the Query Builder after filtering the results
- Custom casts
- Order based on a related model's average or count
- Return transactions result
- Remove several global scopes from query
- Order JSON column attribute
- Get single column's value from the first result
- Check if altered value changed key
- New way to define accessor and mutator
- Another way to do accessors and mutators
- When searching for the first record, you can perform some actions
- Directly convert created_at date to human readable format
- Ordering by an Eloquent Accessor
- Check for specific model was created or found
- Laravel Scout with database driver
- Make use of the value method on the query builder
- Pass array to where method
- Return the primary keys from models collection
- Force Laravel to use eager loading
- Make all your models mass assignable
- Hiding columns in select all statements
- JSON Where Clauses
- Get all the column names for a table
- Compare the values of two columns
- Accessor Caching
- New scalar() method
- Select specific columns
- Chain conditional clauses to the query without writing if-else statements
Typically, we need to query multiple time from a filtered query. So, most of the time we use query()
method,
let's write a query for getting today created active and inactive products
$query = Product::query();
$today = request()->q_date ?? today();
if($today){
$query->where('created_at', $today);
}
// lets get active and inactive products
$active_products = $query->where('status', 1)->get(); // this line modified the $query object variable
$inactive_products = $query->where('status', 0)->get(); // so here we will not find any inactive products
But, after getting $active products
the $query
will be modified. So, $inactive_products
will not find any inactive products from $query
and that will return blank collection every time. Cause, that will try to find inactive products from $active_products
($query
will return active products only).
For solve this issue, we can query multiple time by reusing this $query
object.
So, We need to clone this $query
before doing any $query
modification action.
$active_products = $query->clone()->where('status', 1)->get(); // it will not modify the $query
$inactive_products = $query->clone()->where('status', 0)->get(); // so we will get inactive products from $query
You can pass an array of bindings to most raw query methods to avoid SQL injection.
// This is vulnerable to SQL injection
$fullname = request('full_name');
User::whereRaw("CONCAT(first_name, last_name) = $fullName")->get();
// Use bindings
User::whereRaw("CONCAT(first_name, last_name) = ?", [request('full_name')])->get();
Tip given by @cosmeescobedo
Migration
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->fullText(['title', 'description']);
});
Natural language
Search for something
Comment::whereFulltext(['title', 'description'], 'something')->get();
Natural language with Query Expansion
Search for something
and use the results to perform a larger query
Comment::whereFulltext(['title', 'description'], 'something', ['expanded' => true])->get();
Boolean mode
Search for something
and exclude else
Comment::whereFulltext(['title', 'description'], '+something -else', ['mode' => 'boolean'])->get();
Tip given by @w3Nicolas
The Eloquent collection's merge method uses the id to avoid duplicated models.
But if you are merging collections of different Models, it can cause get you unexpected results.
Use the base collection method instead.
$videos = Video::all();
$images = Image::all();
// If there are videos with the same id as images they will get replaced
// You'll end up with missing videos
$allMedia = $videos->merge($images);
// call `toBase()` in your eloquent collection to use the base merge method instead
$allMedia = $videos->toBase()->merge($images);
Tip given by @cosmeescobedo
If you would like to perform model operations without the model having its updated_at
timestamp modified, you may operate on the model within a closure given to the withoutTimestamps
method.
Available from Laravel 9.31.
$user = User::first();
// `updated_at` is not changed...
User::withoutTimestamps(
fn () => $user->update(['reserved_at' => now()])
);
Tip given by @LaravelEloquent
Using DB::afterCommit
method you can write code that will only be executed if the transaction gets committed and discarded if the transaction is rolled back.
If there is no transaction, the code will be executed right away.
DB::transaction(function () {
$user = User::create([...]);
$user->teams()->create([...]);
});
class User extends Model
{
protected static function booted()
{
static::created(function ($user) {
// Will send the email only if the
// transaction is committed
DB::afterCoommit(function () use ($user) {
Mail::send(new WelcomeEmail($user));
});
});
}
}
Tip given by @cosmeescobedo
Did you know that you can use Eloquent scopes inside of defining other relationships?
app/Models/Lesson.php:
public function scopePublished($query)
{
return $query->where('is_published', true);
}
app/Models/Course.php:
public function lessons(): HasMany
{
return $this->hasMany(Lesson::class);
}
public function publishedLessons(): HasMany
{
return $this->lessons()->published();
}
Laravel 9.37 has a new rawValue()
method to get a value from a SQL expression. Here are some examples from the pull request:
$first = TripModel::orderBy('date_at', 'ASC')
->rawValue('YEAR(`date_at`)');
$last = TripModel::orderBy('date_at', 'DESC')
->rawValue('YEAR(`date_at`)');
$fullname = UserModel::where('id', $id)
->rawValue('CONCAT(`first_name`, " ", `last_name`)');
Tip given by @LoydRG
In Eloquent, check the date with functions whereDay()
, whereMonth()
, whereYear()
, whereDate()
and whereTime()
.
$products = Product::whereDate('created_at', '2018-01-31')->get();
$products = Product::whereMonth('created_at', '12')->get();
$products = Product::whereDay('created_at', '31')->get();
$products = Product::whereYear('created_at', date('Y'))->get();
$products = Product::whereTime('created_at', '=', '14:13:58')->get();
If you want to increment some DB column in some table, just use increment()
function. Oh, and you can increment not only by 1, but also by some number, like 50.
Post::find($post_id)->increment('view_count');
User::find($user_id)->increment('points', 50);
If your DB table doesn't contain timestamp fields created_at
and updated_at
, you can specify that Eloquent model wouldn't use them, with $timestamps = false
property.
class Company extends Model
{
public $timestamps = false;
}
When using soft-deletes, you can restore multiple rows in one sentence.
Post::onlyTrashed()->where('author_id', 1)->restore();
When calling Eloquent's Model::all()
, you can specify which columns to return.
$users = User::all(['id', 'name', 'email']);
In addition to findOrFail()
, there's also Eloquent method firstOrFail()
which will return 404 page if no records for query are found.
$user = User::where('email', '[email protected]')->firstOrFail();
In Eloquent Query Builder, you can specify "as" to return any column with a different name, just like in plain SQL query.
$users = DB::table('users')->select('name', 'email as user_email')->get();
After Eloquent query you can modify rows by using map()
function in Collections.
$users = User::where('role_id', 1)->get()->map(function (User $user) {
$user->some_column = some_function($user);
return $user;
});
What if you’re working with non-Laravel database and your timestamp columns are named differently? Maybe, you have create_time and update_time. Luckily, you can specify them in the model, too:
class Role extends Model
{
const CREATED_AT = 'create_time';
const UPDATED_AT = 'update_time';
}
Instead of:
User::orderBy('created_at', 'desc')->get();
You can do it quicker:
User::latest()->get();
By default, latest()
will order by created_at
.
There is an opposite method oldest()
which would order by created_at
ascending:
User::oldest()->get();
Also, you can specify another column to order by. For example, if you want to use updated_at
, you can do this:
$lastUpdatedUser = User::latest('updated_at')->first();
If you want to generate some DB column value when creating record, add it to model's boot()
method.
For example, if you have a field "position" and want to assign the next available position to the new record (like Country::max('position') + 1)
, do this:
class Country extends Model {
protected static function boot()
{
parent::boot();
Country::creating(function($model) {
$model->position = Country::max('position') + 1;
});
}
}
Use SQL raw queries like whereRaw()
method, to make some DB-specific calculations directly in query, and not in Laravel, usually the result will be faster. Like, if you want to get users that were active 30+ days after their registration, here's the code:
User::where('active', 1)
->whereRaw('TIMESTAMPDIFF(DAY, created_at, updated_at) > ?', 30)
->get();
You can combine and chain Query Scopes in Eloquent, using more than one scope in a query.
Model:
public function scopeActive($query) {
return $query->where('active', 1);
}
public function scopeRegisteredWithinDays($query, $days) {
return $query->where('created_at', '>=', now()->subDays($days));
}
Some Controller:
$users = User::registeredWithinDays(30)->active()->get();
If you're performing whereDate()
and check today's records, you can use Carbon's now()
and it will automatically be transformed to date. No need to do ->toDateString()
.
// Instead of
$todayUsers = User::whereDate('created_at', now()->toDateString())->get();
// No need to convert, just use now()
$todayUsers = User::whereDate('created_at', now())->get();
You can group Eloquent results by any custom condition, here's how to group by first letter of user's name:
$users = User::all()->groupBy(function($item) {
return $item->name[0];
});
If you have DB column which you want to be set only once and never updated again, you can set that restriction on Eloquent Model, with a mutator:
- In version 9 and above:
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Model
{
protected function email(): Attribute
{
return Attribute::make(
set: fn ($value, $attributes) => $attributes['email'] ?? $value,
);
}
}
- In version 9 and below:
class User extends Model
{
public function setEmailAttribute($value)
{
if (isset($this->attributes['email']) && ! is_null($this->attributes['email'])) {
return;
}
$this->attributes['email'] = $value;
}
}
Eloquent method find()
may accept multiple parameters, and then it returns a Collection of all records found, not just one Model:
// Will return Eloquent Model
$user = User::find(1);
// Will return Eloquent Collection
$users = User::find([1,2,3]);
return Product::whereIn('id', $this->productIDs)->get();
// You can do this
return Product::find($this->productIDs)
Tip given by @tahiriqbalnajam
Incase of integer, use whereIn
with limited data range only instead use whereIntegerInRaw
which is faster then whereIn
.
Product::whereIn('id', range(1, 50))->get();
// You can do this
Product::whereIntegerInRaw('id', range(1, 50))->get();
Tip given by @sachinkiranti
Eloquent method find()
may accept multiple parameters, and then it returns a Collection of all records found with specified columns, not all columns of model:
// Will return Eloquent Model with first_name and email only
$user = User::find(1, ['first_name', 'email']);
// Will return Eloquent Collection with first_name and email only
$users = User::find([1,2,3], ['first_name', 'email']);
Tip given by @tahiriqbalnajam
You can also find multiple records with whereKey()
method which takes care of which field is exactly your primary key (id
is the default, but you may override it in Eloquent model):
$users = User::whereKey([1,2,3])->get();
You don't want to use auto incrementing ID in your model?
Migration:
Schema::create('users', function (Blueprint $table) {
// $table->increments('id');
$table->uuid('id')->unique();
});
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use HasUuids;
// ...
}
$article = Article::create(['title' => 'Traveling to Europe']);
$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"
Model:
- In PHP 7.4.0 and above:
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public $incrementing = false;
protected $keyType = 'string';
protected static function boot()
{
parent::boot();
self::creating(fn (User $model) => $model->attributes['id'] = Str::uuid());
self::saving(fn (User $model) => $model->attributes['id'] = Str::uuid());
}
}
- In PHP older than 7.4.0:
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public $incrementing = false;
protected $keyType = 'string';
protected static function boot()
{
parent::boot();
self::creating(function ($model) {
$model->attributes['id'] = Str::uuid();
});
self::saving(function ($model) {
$model->attributes['id'] = Str::uuid();
});
}
}
From Laravel 6, you can use addSelect() in Eloquent statement, and do some calculation to that added column.
return Destination::addSelect(['last_flight' => Flight::select('name')
->whereColumn('destination_id', 'destinations.id')
->orderBy('arrived_at', 'desc')
->limit(1)
])->get();
When doing Eloquent query, if you want to hide specific field from being returned, one of the quickest ways is to add ->makeHidden()
on Collection result.
$users = User::all()->makeHidden(['email_verified_at', 'deleted_at']);
If you want to catch Eloquent Query exceptions, use specific QueryException
instead default Exception class, and you will be able to get the exact SQL code of the error.
try {
// Some Eloquent/SQL statement
} catch (\Illuminate\Database\QueryException $e) {
if ($e->getCode() === '23000') { // integrity constraint violation
return back()->withError('Invalid data');
}
}
Don't forget that soft-deletes will exclude entries when you use Eloquent, but won't work if you use Query Builder.
// Will exclude soft-deleted entries
$users = User::all();
// Will NOT exclude soft-deleted entries
$users = User::withTrashed()->get();
// Will NOT exclude soft-deleted entries
$users = DB::table('users')->get();
If you need to execute a simple SQL query, without getting any results - like changing something in DB schema, you can just do DB::statement()
.
DB::statement('DROP TABLE users');
DB::statement('ALTER TABLE projects AUTO_INCREMENT=123');
If you have two DB operations performed, and second may get an error, then you should rollback the first one, right?
For that, I suggest to use DB Transactions, it's really easy in Laravel:
DB::transaction(function () {
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
});
If you need to check if the record exists, and then update it, or create a new record otherwise, you can do it in one sentence - use Eloquent method updateOrCreate()
:
// Instead of this
$flight = Flight::where('departure', 'Oakland')
->where('destination', 'San Diego')
->first();
if ($flight) {
$flight->update(['price' => 99, 'discounted' => 1]);
} else {
$flight = Flight::create([
'departure' => 'Oakland',
'destination' => 'San Diego',
'price' => 99,
'discounted' => 1
]);
}
// Do it in ONE sentence
$flight = Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99, 'discounted' => 1]
);
Tip given by @pratiksh404
If you have cache key like posts
that gives collection, and you want to forget that cache key on new store or update, you can call static saved
function on your model:
class Post extends Model
{
// Forget cache key on storing or updating
public static function boot()
{
parent::boot();
static::saved(function () {
Cache::forget('posts');
});
}
}
Tip given by @syofyanzuhad
To change the format of created_at
you can add a method in your model like this:
Since Laravel 9:
protected function createdAtFormatted(): Attribute
{
return Attribute::make(
get: fn ($value, $attributes) => $attributes['created_at']->format('H:i d, M Y'),
);
}
Laravel 8 and below:
public function getCreatedAtFormattedAttribute()
{
return $this->created_at->format('H:i d, M Y');
}
So you can use it $entry->created_at_formatted
when it's needed.
It will return the created_at
attribute like this: 04:19 23, Aug 2020
.
And also for changing format of updated_at
attribute, you can add this method :
Since Laravel 9:
protected function updatedAtFormatted(): Attribute
{
return Attribute::make(
get: fn ($value, $attributes) => $attributes['updated_at']->format('H:i d, M Y'),
);
}
Laravel 8 and below:
public function getUpdatedAtFormattedAttribute()
{
return $this->updated_at->format('H:i d, M Y');
}
So you can use it $entry->updated_at_formatted
when it's needed.
It will return the updated_at
attribute like this: 04:19 23, Aug 2020
.
Tip given by @pratiksh404
If you have input field which takes an array and you have to store it as a JSON, you can use $casts
property in your model. Here images
is a JSON attribute.
protected $casts = [
'images' => 'array',
];
So you can store it as a JSON, but when retrieved from DB, it can be used as an array.
If you have two very similar Models (like shipping address and billing address) and you need to make a copy of one to another, you can use replicate()
method and change some properties after that.
Example from the official docs:
$shipping = Address::create([
'type' => 'shipping',
'line_1' => '123 Example Street',
'city' => 'Victorville',
'state' => 'CA',
'postcode' => '90001',
]);
$billing = $shipping->replicate()->fill([
'type' => 'billing'
]);
$billing->save();
Sometimes we need to load a huge amount of data into memory. For example:
$orders = Order::all();
But this can be slow if we have really huge data, because Laravel prepares objects of the Model class.
In such cases, Laravel has a handy function toBase()
$orders = Order::toBase()->get();
//$orders will contain `Illuminate\Support\Collection` with objects `StdClass`.
By calling this method, it will fetch the data from the database, but it will not prepare the Model class. Keep in mind it is often a good idea to pass an array of fields to the get method, preventing all fields to be fetched from the database.
If you create a Laravel boilerplate as a "starter" for other devs, and you're not in control of what THEY would later fill in Model's $fillable/$guarded, you may use forceFill()
$team->update(['name' => $request->name])
What if "name" is not in Team model's $fillable
? Or what if there's no $fillable/$guarded
at all?
$team->forceFill(['name' => $request->name])
This will "ignore" the $fillable
for that one query and will execute no matter what.
If you have a 3-level structure of parent-children, like categories in an e-shop, and you want to show the number of products on the third level, you can use with('yyy.yyy')
and then add withCount()
as a condition
class HomeController extend Controller
{
public function index()
{
$categories = Category::query()
->whereNull('category_id')
->with(['subcategories.subcategories' => function($query) {
$query->withCount('products');
}])->get();
}
}
class Category extends Model
{
public function subcategories()
{
return $this->hasMany(Category::class);
}
public function products()
{
return $this->hasMany(Product::class);
}
}
<ul>
@foreach($categories as $category)
<li>
{{ $category->name }}
@if ($category->subcategories)
<ul>
@foreach($category->subcategories as $subcategory)
<li>
{{ $subcategory->name }}
@if ($subcategory->subcategories)
<ul>
@foreach ($subcategory->subcategories as $subcategory)
<li>{{ $subcategory->name }} ({{ $subcategory->product_count }})</li>
@endforeach
</ul>
@endif
</li>
@endforeach
</ul>
@endif
</li>
@endforeach
</ul>
When looking for a record, you may want to perform some actions if it's not found.
In addition to ->firstOrFail()
which just throws 404, you can perform any action on failure, just do ->firstOr(function() { ... })
$model = Flight::where('legs', '>', 3)->firstOr(function () {
// ...
})
Don't use find() and then check if the record exists. Use findOrFail().
$product = Product::find($id);
if (!$product) {
abort(404);
}
$product->update($productDataArray);
Shorter way
$product = Product::findOrFail($id); // shows 404 if not found
$product->update($productDataArray);
abort_if()
can be used as shorter way to check condition and throw an error page.
$product = Product::findOrFail($id);
if($product->user_id != auth()->user()->id){
abort(403);
}
Shorter way
/* abort_if(CONDITION, ERROR_CODE) */
$product = Product::findOrFail($id);
abort_if ($product->user_id != auth()->user()->id, 403)
If you want to fill a column automatically while you persist data to the database (e.g: slug) use Model Observer instead of hard code it every time
use Illuminate\Support\Str;
class Article extends Model
{
...
protected static function boot()
{
parent:boot();
static::saving(function ($model) {
$model->slug = Str::slug($model->title);
});
}
}
Tip given by @sky_0xs
You can call the explain()
method on queries to know extra information about the query.
Book::where('name', 'Ruskin Bond')->explain()->dd();
Illuminate\Support\Collection {#5344
all: [
{#15407
+"id": 1,
+"select_type": "SIMPLE",
+"table": "books",
+"partitions": null,
+"type": "ALL",
+"possible_keys": null,
+"key": null,
+"key_len": null,
+"ref": null,
+"rows": 9,
+"filtered": 11.11111164093,
+"Extra": "Using where",
},
],
}
Tip given by @amit_merchant
// This works
if ( 0 === $model->where('status', 'pending')->count() ) {
}
// But since I don't care about the count, just that there isn't one
// Laravel's exists() method is cleaner.
if ( ! $model->where('status', 'pending')->exists() ) {
}
// But I find the ! in the statement above easily missed. The
// doesntExist() method makes this statement even clearer.
if ( $model->where('status', 'pending')->doesntExist() ) {
}
Tip given by @ShawnHooper
If you have a Trait that you want to add to a few Models to call their boot()
method automatically, you can call Trait's method as boot[TraitName]
class Transaction extends Model
{
use MultiTenantModelTrait;
}
class Task extends Model
{
use MultiTenantModelTrait;
}
trait MultiTenantModelTrait
{
// This method's name is boot[TraitName]
// It will be auto-called as boot() of Transaction/Task
public static function bootMultiTenantModelTrait()
{
static::creating(function ($model) {
if (!$isAdmin) {
$isAdmin->created_by_id = auth()->id();
}
})
}
}
There are two common ways of determining if a table is empty in Laravel. Calling exists()
or count()
directly on the model!
One returns a strict true/false boolean, the other returns an integer which you can use as a falsy in conditionals.
public function index()
{
if (\App\Models\User::exists()) {
// returns boolean true or false if the table has any saved rows
}
if (\App\Models\User::count()) {
// returns the count of rows in the table
}
}
Tip given by @aschmelyun
// BelongsTo Default Models
// Let's say you have Post belonging to Author and then Blade code:
$post->author->name;
// Of course, you can prevent it like this:
$post->author->name ?? ''
// or
@$post->author->name
// But you can do it on Eloquent relationship level:
// this relation will return an empty App\Author model if no author is attached to the post
public function author() {
return $this->belongsTo('App\Author')->withDefault();
}
// or
public function author() {
return $this->belongsTo('App\Author')->withDefault([
'name' => 'Guest Author'
]);
}
Tip given by @coderahuljat
Get original attributes after mutating an Eloquent record you can get the original attributes by calling getOriginal()
$user = App\User::first();
$user->name; // John
$user->name = "Peter"; // Peter
$user->getOriginal('name'); // John
$user->getOriginal(); // Original $user record
Tip given by @devThaer
A simple way to seed a database in Laravel with a .sql dump file
DB::unprepared(
file_get_contents(__DIR__ . './dump.sql')
);
Tip given by @w3Nicolas
Using the CROSS JOIN subquery
use Illuminate\Support\Facades\DB;
$totalQuery = DB::table('orders')->selectRaw('SUM(price) as total');
DB::table('orders')
->select('*')
->crossJoinSub($totalQuery, 'overall')
->selectRaw('(price / overall.total) * 100 AS percent_of_total')
->get();
Tip given by @PascalBaljet
To determine the table name of the relationship's intermediate table, Eloquent will join the two related model names in alphabetical order.
This would mean a join between Post
and Tag
could be added like this:
class Post extends Model
{
public $table = 'posts';
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
However, you are free to override this convention, and you would need to specify the join table in the second argument.
class Post extends Model
{
public $table = 'posts';
public function tags()
{
return $this->belongsToMany(Tag::class, 'posts_tags');
}
}
If you wish to be explicit about the primary keys you can also supply these as third and fourth arguments.
class Post extends Model
{
public $table = 'posts';
public function tags()
{
return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id');
}
}
Tip given by @iammikek
BelongsToMany::orderByPivot()
allows you to directly sort the results of a BelongsToMany relationship query.
class Tag extends Model
{
public $table = 'tags';
}
class Post extends Model
{
public $table = 'posts';
public function tags()
{
return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id')
->using(PostTagPivot::class)
->withTimestamps()
->withPivot('flag');
}
}
class PostTagPivot extends Pivot
{
protected $table = 'post_tag';
}
// Somewhere in the Controller
public function getPostTags($id)
{
return Post::findOrFail($id)->tags()->orderByPivot('flag', 'desc')->get();
}
Tip given by @PascalBaljet
The sole()
method will return only one record that matches the criteria. If no such entry is found, then a NoRecordsFoundException
will be thrown. If multiple records are found, then a MultipleRecordsFoundException
will be thrown.
DB::table('products')->where('ref', '#123')->sole();
Tip given by @PascalBaljet
Similar to each()
method, but easier to use. Automatically splits the result into parts (chunks).
return User::orderBy('name')->chunkMap(fn ($user) => [
'id' => $user->id,
'name' => $user->name,
]), 25);
Tip given by @PascalBaljet
Sometimes you need to update the model without sending any events. We can now do this with the updateQuietly()
method, which under the hood uses the saveQuietly()
method.
$flight->updateQuietly(['departed' => false]);
Tip given by @PascalBaljet
To periodically clean models of obsolete records. With this trait, Laravel will do this automatically, only you need to adjust the frequency of the model:prune
command in the Kernel class.
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
class Flight extends Model
{
use Prunable;
/**
* Get the prunable model query.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function prunable()
{
return static::where('created_at', '<=', now()->subMonth());
}
}
Also, in the pruning method, you can set the actions that must be performed before deleting the model:
protected function pruning()
{
// Removing additional resources,
// associated with the model. For example, files.
Storage::disk('s3')->delete($this->filename);
}
Tip given by @PascalBaljet
Laravel 8.53 introduces the immutable_date
and immutable_datetime
castes that convert dates to Immutable
.
Cast to CarbonImmutable instead of a regular Carbon instance.
class User extends Model
{
public $casts = [
'date_field' => 'immutable_date',
'datetime_field' => 'immutable_datetime',
];
}
Tip given by @PascalBaljet
The findOrFail method also accepts a list of ids. If any of these ids are not found, then it "fails".
Nice if you need to retrieve a specific set of models and don't want to have to check that the count you got was the count you expected
User::create(['id' => 1]);
User::create(['id' => 2);
User::create(['id' => 3]);
// Retrieves the user...
$user = User::findOrFail(1);
// Throws a 404 because the user doesn't exist...
User::findOrFail(99);
// Retrieves all 3 users...
$users = User::findOrFail([1, 2, 3]);
// Throws because it is unable to find *all* of the users
User::findOrFail([1, 2, 3, 99]);
Tip given by @timacdonald87
New in Laravel 8.50: You can use the Prunable trait to automatically remove models from your database. For example, you can permanently remove soft deleted models after a few days.
class File extends Model
{
use SoftDeletes;
// Add Prunable trait
use Prunable;
public function prunable()
{
// Files matching this query will be pruned
return static::query()->where('deleted_at', '<=', now()->subDays(14));
}
protected function pruning()
{
// Remove the file from s3 before deleting the model
Storage::disk('s3')->delete($this->filename);
}
}
// Add PruneCommand to your schedule (app/Console/Kernel.php)
$schedule->command(PruneCommand::class)->daily();
Tip by @Philo01
Under the hood, the withAvg/withCount/withSum and other methods in Eloquent use the 'withAggregate' method. You can use this method to add a subselect based on a relationship
// Eloquent Model
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
// Instead of eager loading all users...
$posts = Post::with('user')->get();
// You can add a subselect to only retrieve the user's name...
$posts = Post::withAggregate('user', 'name')->get();
// This will add a 'user_name' attribute to the Post instance:
$posts->first()->user_name;
Tip given by @pascalbaljet
Using the something_at
convention instead of just a boolean in Laravel models gives you visibility into when a flag was changed – like when a product went live.
// Migration
Schema::table('products', function (Blueprint $table) {
$table->datetime('live_at')->nullable();
});
// In your model
public function live()
{
return !is_null($this->live_at);
}
// Also in your model
protected $dates = [
'live_at'
];
Tip given by @alexjgarrett
The upsert() method will insert or update multiple records.
- First array: the values to insert or update
- Second: unique identifier columns used in the select statement
- Third: columns that you want to update if the record exists
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150],
], ['departure', 'destination'], ['price']);
Tip given by @mmartin_joo
To retrieve the Query Builder after filtering the results: you can use ->toQuery()
.
The method internally use the first model of the collection and a whereKey
comparison on the Collection models.
// Retrieve all logged_in users
$loggedInUsers = User::where('logged_in', true)->get();
// Filter them using a Collection method or php filtering
$nthUsers = $loggedInUsers->nth(3);
// You can't do this on the collection
$nthUsers->update(/* ... */);
// But you can retrieve the Builder using ->toQuery()
if ($nthUsers->isNotEmpty()) {
$nthUsers->toQuery()->update(/* ... */);
}
Tip given by @RBilloir
You can create custom casts to have Laravel automatically format your Eloquent model data. Here's an example that capitalises a user's name when it is retrieved or changed.
class CapitalizeWordsCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return ucwords($value);
}
public function set($model, string $key, $value, array $attributes)
{
return ucwords($value);
}
}
class User extends Model
{
protected $casts = [
'name' => CapitalizeWordsCast::class,
'email' => 'string',
];
}
Tip given by @mattkingshott
Did you ever need to order based on a related model's average or count?
It's easy with Eloquent!
public function bestBooks()
{
Book::query()
->withAvg('ratings as average_rating', 'rating')
->orderByDesc('average_rating');
}
Tip given by @mmartin_joo
If you have a DB transaction and want to return its result, there are at least two ways, see the example
// 1. You can pass the parameter by reference
$invoice = NULL;
DB::transaction(function () use (&$invoice) {
$invoice = Invoice::create(...);
$invoice->items()->attach(...);
})
// 2. Or shorter: just return trasaction result
$invoice = DB::transaction(function () {
$invoice = Invoice::create(...);
$invoice->items()->attach(...);
return $invoice;
});
When using Eloquent Global Scopes, you not only can use MULTIPLE scopes, but also remove certain scopes when you don't need them, by providing the array to withoutGlobalScopes()
// Remove all of the global scopes...
User::withoutGlobalScopes()->get();
// Remove some of the global scopes...
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
With Eloquent you can order results by a JSON column attribute
// JSON column example:
// bikes.settings = {"is_retired": false}
$bikes = Bike::where('athlete_id', $this->athleteId)
->orderBy('name')
->orderByDesc('settings->is_retired')
->get();
Tip given by @brbcoding
You can use value()
method to get single column's value from the first result of a query
// Instead of
Integration::where('name', 'foo')->first()->active;
// You can use
Integration::where('name', 'foo')->value('active');
// or this to throw an exception if no records found
Integration::where('name', 'foo')->valueOrFail('active')';
Tip given by @justsanjit
Ever wanted to know if the changes you've made to a model have altered the value for a key? No problem, simply reach for originalIsEquivalent.
$user = User::first(); // ['name' => "John']
$user->name = 'John';
$user->originalIsEquivalent('name'); // true
$user->name = 'David'; // Set directly
$user->fill(['name' => 'David']); // Or set via fill
$user->originalIsEquivalent('name'); // false
Tip given by @mattkingshott
New way to define attribute accessors and mutators in Laravel 8.77:
// Before, two-method approach
public function setTitleAttribute($value)
{
$this->attributes['title'] = strtolower($value);
}
public function getTitleAttribute($value)
{
return strtoupper($value);
}
// New approach
protected function title(): Attribute
{
return new Attribute(
get: fn ($value) => strtoupper($value),
set: fn ($value) => strtolower($value),
);
}
Tip given by @Teacoders
In case you are going to use the same accessors and mutators in many models , You can use custom casts instead.
Just create a class
that implements CastsAttributes
interface. The class should have two methods, the first is get
to specify how models should be retrieved from the database and the second is set
to specify how the value will be stored in the database.
<?php
namespace App\Casts;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class TimestampsCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return Carbon::parse($value)->diffForHumans();
}
public function set($model, string $key, $value, array $attributes)
{
return Carbon::parse($value)->format('Y-m-d h:i:s');
}
}
Then you can implement the cast in the model class.
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Casts\TimestampsCast;
use Carbon\Carbon;
class User extends Authenticatable
{
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'updated_at' => TimestampsCast::class,
'created_at' => TimestampsCast::class,
];
}
Tip given by @AhmedRezk
When searching for the first record, you want to perform some actions, when you don't find it. firstOrFail()
throws a 404 Exception.
You can use firstOr(function() {})
instead. Laravel got your covered
$book = Book::whereCount('authors')
->orderBy('authors_count', 'DESC')
->having('modules_count', '>', 10)
->firstOr(function() {
// The Sky is the Limit ...
// You can perform any action here
});
Tip given by @bhaidar
Did you know you can directly convert created_at date to human readable format like 1 minute ago, 1 month ago using diffForHumans() function. Laravel eloquent by default enables Carbon instance on created_at field.
$post = Post::whereId($id)->first();
$result = $post->created_at->diffForHumans();
/* OUTPUT */
// 1 Minutes ago, 2 Week ago etc..as per created time
Tip given by @vishal__2931
Ordering by an Eloquent Accessor! Yes, that's doable. Instead of ordering by the accessor on the DB level, we order by the accessor on the returned Collection.
class User extends Model
{
// ...
protected $appends = ['full_name'];
// Since Laravel 9
protected function full_name(): Attribute
{
return Attribute::make(
get: fn ($value, $attributes) => $attributes['first_name'] . ' ' . $attributes['last_name'];),
);
}
// Laravel 8 and lower
public function getFullNameAttribute()
{
return $this->attribute['first_name'] . ' ' . $this->attributes['last_name'];
}
// ..
}
class UserController extends Controller
{
// ..
public function index()
{
$users = User::all();
// order by full_name desc
$users->sortByDesc('full_name');
// or
// order by full_name asc
$users->sortBy('full_name');
// ..
}
// ..
}
sortByDesc
and sortBy
are methods on the Collection
Tip given by @bhaidar
If you want to check for specific model was created or found, use wasRecentlyCreated
model attribute.
$user = User::create([
'name' => 'Oussama',
]);
// return boolean
return $user->wasRecentlyCreated;
// true for recently created
// false for found (already on you db)
Tip given by @sky_0xs
With laravel v9 you can use Laravel Scout (Search) with database driver. No more where likes!
$companies = Company::search(request()->get('search'))->paginate(15);
Tip given by @magarrent
Make use of the value
method on the query builder to execute a more efficient query when you only need to retrieve a single column.
// Before (fetches all columns on the row)
Statistic::where('user_id', 4)->first()->post_count;
// After (fetches only `post_count`)
Statistic::where('user_id', 4)->value('post_count');
Tip given by @mattkingshott
Laravel you can pass an array to the where method.
// Instead of this
JobPost::where('company', 'laravel')
->where('job_type', 'full time')
->get();
// You can pass an array
JobPost::where(['company' => 'laravel',
'job_type' => 'full time'])
->get();
Tip given by @cosmeescobedo
Did you know modelsKeys()
eloquent collection method? It returns the primary keys from models collection.
$users = User::active()->limit(3)->get();
$users->modelsKeys(); // [1, 2, 3]
Tip given by @iamharis010
If you want to prevent a lazy loading in your app, you only need to add following line to the boot()
method in your AppServiceProvider
Model::preventLazyLoading();
But, if you want to enable this feature only on your local development you can change above code on that:
Model::preventLazyLoading(!app()->isProduction());
Tip given by @CatS0up
It is not a recommended approach for security reasons, but it is possible.
When you want do this, you don't need to set an empty $guarded
array for every model, like this:
protected $guarded = [];
You can do it from a single place, just add following line to the boot()
method in your AppServiceProvider
:
Model::unguard();
Now, all your models are mass assignable.
Tip given by @CatS0up
If you use Laravel v8.78 and MySQL 8.0.23 and onwards, you can define choosen columns as "invisible". Columns which are define as invisible
will be hidden from the select *
statements.
However, to do so, we must use a invisible()
method in the migration, something like that:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('table', function (Blueprint $table) {
$table->string('secret')->nullable()->invisible();
});
That's it! This will make chosen column hidden from select *
statement.
Tip given by @CatS0up
Laravel offers helpers to query JSON columns for databases that support them.
Currently, MySQL 5.7+, PostgreSQL, SQL Server 2016, and SQLite 3.9.0 (using the JSON1 extension)
// To query a json column you can use the -> operator
$users = User::query()
->where('preferences->dining->meal', 'salad')
->get();
// You can check if a JSON array contains a set of values
$users = User::query()
->whereJsonContains('options->languages', [
'en', 'de'
])
->get();
// You can also query by the length a JSON array
$users = User::query()
->whereJsonLength('options->languages', '>', 1)
->get();
Tip given by @cosmeescobedo
DB::getSchemaBuilder()->getColumnListing('users');
/*
returns [
'id',
'name',
'email',
'email_verified_at',
'password',
'remember_token',
'created_at',
'updated_at',
];
*/
Tip given by @aaronlumsden
You can use whereColumn
method to compare the values of two columns.
return Task::whereColumn('created_at', 'updated_at')->get();
// pass a comparison operator
return Task::whereColumn('created_at', '>', 'updated_at')->get();
Tip given by @iamgurmandeep
As of Laravel 9.6, if you have a computationally intensive accessor, you can use the shouldCache method.
public function hash(): Attribute
{
return Attribute::make(
get: fn($value) => bcrypt(gzuncompress($value)),
)->shouldCache();
}
Tip given by @cosmeescobedo
In Laravel 9.8.0, the scalar()
method was added that allows you to retrieve the first column of the first row from the query result.
// Before
DB::selectOne("SELECT COUNT(CASE WHEN food = 'burger' THEN 1 END) AS burgers FROM menu_items;")->burgers
// Now
DB::scalar("SELECT COUNT(CASE WHEN food = 'burger' THEN 1 END) FROM menu_items;")
Tip given by @justsanjit
To select specific columns on a model you can use the select method -- or you can pass an array directly to the get method!
// Select specified columns from all employees
$employees = Employee::select(['name', 'title', 'email'])->get();
// Select specified columns from all employees
$employees = Employee::get(['name', 'title', 'email']);
Tip given by @ecrmnn
The "when" helper in the query builder is🔥
You can chain conditional clauses to the query without writing if-else statements.
Makes your query very clear:
class RatingSorter extends Sorter
{
function execute(Builder $query)
{
$query
->selectRaw('AVG(product_ratings.rating) AS avg_rating')
->join('product_ratings', 'products.id', '=', 'product_ratings.product_id')
->groupBy('products.id')
->when(
$this->direction === SortDirections::Desc,
fn () => $query->orderByDesc('avg_rating')
fn () => $query->orderBy('avg_rating'),
);
return $query;
}
}
Tip given by @mmartin_joo