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', 'povilas@laraveldaily.com')->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 thisreturn 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 thisProduct::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 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.phppublic function delete(){//extra steps here whatever you want//now perform the normal deletionModel::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 worksif ( 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 postpublic function author() { return $this->belongsTo('App\Author')->withDefault();}// orpublic 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 Controllerpublic 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 usersUser::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 Modelclass 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.
// MigrationSchema::table('products', function (Blueprint $table) { $table->datetime('live_at')->nullable();});// In your modelpublic function live(){ return !is_null($this->live_at);}// Also in your modelprotected $dates = [ 'live_at'];
Tip given by @alexjgarrett
The upsert() method will insert or update multiple records.
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 ofIntegration::where('name', 'foo')->first()->active;// You can useIntegration::where('name', 'foo')->value('active');// or this to throw an exception if no records foundIntegration::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 approachpublic function setTitleAttribute($value){ $this->attributes['title'] = strtolower($value);}public function getTitleAttribute($value){ return strtoupper($value);} // New approachprotected 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.
<?phpnamespace 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.
<?phpnamespace 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 readble format like 1 miniute 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']; 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 booleanreturn $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 thisJobPost::where('company', 'laravel') ->where('job_type', 'full time') ->get();// You can pass an arrayJobPost::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 operatorreturn 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.
// BeforeDB::selectOne("SELECT COUNT(CASE WHEN food = 'burger' THEN 1 END) AS burgers FROM menu_items;")->burgers// NowDB::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