⬆️ Go to main menu ➡️ Next (Models Relations)
- Reuse or clone query
- 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
- Perform any extra steps before deleting model
- 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
- Order by Pivot Fields
- Belongs to Many Pivot table naming
- 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
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
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:
class User extends Model
{
public function setEmailAttribute($value)
{
if ($this->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
Eloquent method find()
may accept multiple parameters, and then it returns a Collection of all records found with specificied 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();
});
Model:
class User extends Model
{
public $incrementing = false;
protected $keyType = 'string';
protected static function boot()
{
parent::boot();
User::creating(function ($model) {
$model->setId();
});
}
public function setId()
{
$this->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:
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 :
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)
Tip given by @back2Lobby
We can use Model::delete()
in the overridden delete method to perform additional steps.
// App\Models\User.php
public function delete(){
//extra steps here whatever you want
//now perform the normal deletion
Model::delete();
}
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]);
// Retrives the user...
$user = User::findOrFail(1);
// Throws a 404 because the user doesn't exist...
User::findOrFail(99);
// Retrives 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()
Link to docs
// 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 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