Eloquent & Database Tips (cd ..)
- Get Original Attributes
- Specify Custom Factories Path
- Eager Loading with Specific Columns
is()
Β method in Laravel- A shorter "whereHas"
- Dynamic Wheres
- Quietly update your models
- Find Related IDs on a BelongsToMany Relationship
- Get All Executed Queries
- The "Prunable" trait
- The "whereBelongsTo" method
- The "whenQueryingForLongerThan" method
- The "foreignIdFor" Method
- Careful with "whereYear()"
- The "withCount" Method
- The "toQuery" method
- The "whenTableDoesntHaveColumn" and "whenTableHasColumn" methods
- The "withoutTimestamps" method
- Random Ordering
- Touching Relationships
- Recursively Saving Models and Relationships
- The "saveMany" method
- Query JSON Fields
- The "toBase()" Method
- The "value()" Method
- The "whereAll" and "whereAny" Methods
- Limit Eager Loaded Relationships
- Define Casts as a Method
- The "withExists" Method
- The "whereKey" Method
- Avoid Columns Ambiguity
- The "doesntHave" Method
- Mute All Model Events
- Make use of "unguard"
- Customize the pivot Attribute Name
- Increment and Decrement Methods
- Check if the Value of a Given Model Key Has Changed
- The "getOrPut" Method
- Use the Higher Order "orWhere" Method
- Faster Queries with "whereIntegerInRaw"
- The "upsert" Method
- No timestamp columns
- The "simplePaginate" Method
- The "doesntExist" Method
- Clone Your Queries
- Hide Columns On The Fly
- Disable Global Scopes
- Hash Passwords Automatically
- The "firstOr" Laravel
- The "latest" and "oldest" Methods
- Insert Or Ignore
- Find Many
- Check If Your Model Has Changed Since Last Retrieval
- Customize the Default Timestamp Columns
- Prevent Filling Unfillable Attributes
- Create New Records or Update Existing Ones
- Delete (Destroy) Records
- Cast Values On The Fly
- The "valueOrFail" Method
- The "firstWhere" Method
- Prevent N+1 Issues
- Check if valid JSON
- Count words occurances
- Shortcuts for Dropping Columns
- Invisible Columns
- Generated Columns
- Disable Model Events When Seeding
- Use Default Models
- Permanently Delete Soft-Deleted Models
- Get Only Trashed Records
- Add Multiple Columns After Another
- Cache Accessor Result
- The "whereLike" method
- Using Multiple IDs and Specific Columns with "find"
- Load Relationship Count on the Fly
- Move Column to First Position
- Latest of Many
- A Cleaner Eager Loading Syntax
- The "insertGetId" Method
- The "ddRawSql" Method
- Avoid Duplicate Queries
- The New "CollectedBy" Attribute
- Aggregate Functions
- The "toggle" method
- Get the Full Query Log
- The New "rawColumn" Method
- Explain Eloquent Queries
- The "firstOrNew" Method
- The "withWhereHas" Method
- Update Pivot Columns
- Prevent Accessing Missing Attributes
Laravel Tip π‘: Get Original Attributes (β¬οΈ)
Laravel accessors allow you to transform your model attributes when retrieving them. But sometimes, you may wish to get the original value. Well, Laravel provides a method just for that: getRawOriginal
π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Model
{
protected function username(): Attribute
{
return Attribute::make(
function (string $value, array $attributes) {
return sprintf('%s#%s', $value, $attributes['app_id']);
}
);
}
}
$user = User::create([
'username' => 'oussama',
]);
$user->getOriginal('username'); // oussama#1234
$user->getRawOriginal('username'); // oussama
Laravel Tip π‘: Specify Custom Factories Path (β¬οΈ)
Laravel automatically resolves model factories if they exist in the Database\Factories namespace. Sometimes you wish to move them. For example, if you're using Domain-Driven Design (DDD), you may want each domain to have its own factories. In such cases, you can instruct Laravel on how to resolve the factory by defining a newFactory method in your model. By doing so, you can perform additional logic too! For example return different factories for different environments.
<?php
namespace App\Models;
use Database\Factories\UserFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
// ...
protected static function newFactory()
{
// ... custom logic
return new UserFactory();
}
}
Laravel Tip π‘: Eager Loading with Specific Columns (β¬οΈ)
Did you know that when using eager loading with relationships, you may specify the exact columns you need? This will decrease memory usage π
<?php
$users = Post::with('author:id,name')->get();
Laravel Tip π‘: is()
Β method in Laravel (β¬οΈ)
Did you know you can check whether or not two models are the same by using the is()
helper? π
<?php
$user = User::find(1);
$sameUser = User::find(1);
$differentUser = User::find(2);
$user->is($sameUser); // true
$user->is($differentUser); // false
Laravel Tip π‘: A shorter "whereHas" (β¬οΈ)
While Laravel's whereHas
is excellent for retrieving records based on a specified relationship along with additional query constraints, there's a shortcut called "whereRelation" that accomplishes the same task π
<?php
// Before
User::whereHas('comments', function ($query) {
$query->where('created_at', '>', now()->subDay());
})->get();
// After
User::whereRelation('comments', 'created_at', '>', now()->subDay())->get();
Laravel Tip π‘: Dynamic Wheres (β¬οΈ)
Did you know that Laravel allows you to define dynamic "where" conditions? For example, you could do, whereNameAndAge(name_value, age_value)
π€―
Make sure to add the method name to your model's PHPDoc so your IDE does not complain, that's a bit too much magic for it to understand.
Curious about how it's done? Take a look at Illuminate\Database\Query\Builder::dynamicWhere()
<?php
// select * from `users` where `name` = 'oussama' and `last_name` = 'mater'"
User::whereNameAndLastName('oussama', 'mater')->first();
Laravel Tip π‘: Quietly update your models (β¬οΈ)
When updating your Laravel models, you always trigger "Model Events," which are hooks enabling you to perform extra actions. You can disable this behavior by updating them quietly π€«
<?php
$user = Auth::user();
$user->name = 'Oussama Mater';
// Won't trigger Model Events
$user->saveQuietly();
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();
Laravel Tip π‘: Find Related IDs on a BelongsToMany Relationship (β¬οΈ)
Did you know that Laravel ships with the 'allRelatedId()' method to help you fetch all IDs for a belongsToMany relationship? Now you do π
<?php
class User extends Model
{
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
$user = User::find(1);
$roleIds = $user->roles()->pluck('id')->toArray();
$roleIds = $user->roles()->allRelatedIds()->toArray();
Laravel Tip π‘: Get All Executed Queries (β¬οΈ)
Did you know you can listen to all executed queries in Laravel? This is not only useful for quick debugging; for instance, you can send a Slack notification if the query is slower than expectedπ
<?php
DB::listen(function (QueryExecuted $query) {
dump($query->sql); // select * from `users` where `users`.`id` = ? limit 1
dump($query->bindings); // [0 => 1]
dump($query->time); // 6.05
});
Laravel Tip π‘: The "Prunable" trait (β¬οΈ)
Did you know that Laravel comes with a Prunable trait to permanently remove records, including the soft-deleted ones, based on a condition you define? π
<?php
// Define the pruning condition
class User extends Authenticatable
{
use Prunable;
public function prunable()
{
return static::query()
->whereNull('email_verified_at')
->where('created_at', '<', now()->subMonths(6));
}
}
// Schedule the pruning command to run daily for example
$schedule->command(PruneCommand::class)->daily();
Laravel Tip π‘: The "whereBelongsTo" method (β¬οΈ)
Did you know that Laravel ships with a "whereBelongsTo" to get the parent model? This will make the code much more readable π
<?php
// This
$posts = Post::where('user_id', $user->id)->get();
// Or this is okay
$posts = Post::whereUserId($user->id)->get();
// But this is more readable
$posts = Post::whereBelongsTo($user)->get();
Laravel Tip π‘: The "whenQueryingForLongerThan" method (β¬οΈ)
Did you know that you can use "whenQueryingForLongerThan" to monitor slow queries? You can set the threshold in milliseconds. If a query exceeds the threshold, you can send notifications or grab a coffee with the mastermind behind the query π
<?php
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
// Mastermind behind this query, let's grab a coffee ..
});
}
}
Laravel Tip π‘: The "foreignIdFor" Method (β¬οΈ)
When defining foreign IDs, Laravel offers multiple methods, one of which is "foreignIdFor()". This method snake cases the model name and appends "id" to it. Not only does it make your code more readable, but you can quickly navigate to the model from the migration π
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->foreignIdFor(User::class); // user_id
$table->timestamps();
});
Laravel Tip π‘: Careful with "whereYear()" (β¬οΈ)
Be careful when using whereYear
; even if your column is indexed, it won't be used, and the database will perform a full table scan. Instead, opt for using ranges π
<?php
DB::table('user_posts')
->select('user_id', DB::raw('COUNT(*) as count'))
->whereYear('created_at', 2023)
->groupBy('user_id')
->get();
// select `user_id`, COUNT(*) as count from `user_posts` where year(`created_at`) = ? group by `user_id`
DB::table('user_posts')
->select('user_id', DB::raw('COUNT(*) as count'))
->whereBetween('created_at', ['2023-01-01 00:00:00', '2024-01-01 00:00:00'])
->groupBy('user_id')
->get();
// select `user_id`, COUNT(*) as count from `user_posts` where `created_at` between ? and ? group by `user_id`
Laravel Tip π‘: The "withCount" Method (β¬οΈ)
Did you know that you can use "withCount" to get the count of a related relationship without loading it? This is really useful, for example, when displaying statistics π
<?php
$users = User::withCount(['posts'])->get();
// $users->posts_count
Laravel Tip π‘: The "toQuery" method (β¬οΈ)
Did you know that Laravel ships with a method called "toQuery"? This method allows you to update a collection using a single query statement by running a "whereIn" π
<?php
$users = User::where('status', 'VIP')->get();
// Instead of this
foreach ($users as $user) {
$user->update(['status' => 'Administrator']);
}
// Do this instead
$users->toQuery()->update(['status' => 'Administrator']);
Laravel Tip π‘: The "whenTableDoesntHaveColumn" and "whenTableHasColumn" methods (β¬οΈ)
Laravel 9 and onward ship with 2 schema methods, 'whenTableDoesntHaveColumn' and 'whenTableHasColumn', which allow you to drop or create a column if it exists or not. This is really helpful when you have multiple environments where schemas can get out of sync quickly.
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// Will create the email column only if it does not exist
Schema::whenTableDoesntHaveColumn('users', 'email', function(Blueprint $table){
$table->string('email')->unique();
});
// Will drop the email column only if it exists
Schema::whenTableHasColumn('users', 'email', function(Blueprint $table){
$table->dropColumn('email');
});
Laravel Tip π‘: The "withoutTimestamps" method (β¬οΈ)
Did you know that if you want to update a model without modifying its updated_at
timestamp, you can use the 'withoutTimestamps' method? π
<?php
// The updated_at column will not be updated
Post::withoutTimestamps(fn () => $post->increment(['reads']));
Laravel Tip π‘: Random Ordering (β¬οΈ)
Did you know that Laravel comes with the "inRandomOrder" method, which sorts query results randomly? π
$randomUser = DB::table('users')
->inRandomOrder()
->first();
Laravel Tip π‘: Touching Relationships (β¬οΈ)
Laravel automatically updates "updated_at" in many-to-many relationships, and it also ships with "setTouchedRelations" method to manually update related models in one-to-one and one-to-many relationships π
<?php
$user = User::firstOrFail();
$user->setTouchedRelations(['posts']);
// The 'updated_at' of all related posts will be updated
$user->save();
Laravel Tip π‘: Recursively Saving Models and Relationships (β¬οΈ)
Did you know that Laravel ships with the "push" method, allowing you to save models and all related relationships recursively, without having to go over them one by one? π
<?php
$post = Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
// Instead of these
$post->comments[0]->author->save();
$post->comments[0]->save();
// You can do this
$post->push();
Laravel Tip π‘: The "saveMany" method (β¬οΈ)
Did you know that Laravel allows you to save multiple related models at once by using the 'saveMany' method? π
<?php
$post = Post::find(1);
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);
Laravel Tip π‘: Query JSON Fields (β¬οΈ)
Did you know that Laravel allows you to query JSON fields in databases that support JSON column types? π
<?php
// Will return all users where the preferences.dining.meal field is equal to 'salad'
DB::table('users')
->where('preferencesβdiningβmeal', 'salad')
->get();
// Will return all users where the languages array contains 'en' and 'de'
DB::table('users')
->whereJsonContains('optionsβlanguages', ['en', 'de'])
->get();
// Will return all users where the languages array has more than one element
DB::table('users')
->whereJsonLength('optionsβlanguages', '>', 1)
->get();
Laravel Tip π‘: The "toBase()" Method (β¬οΈ)
Sometimes, you may need to load a large amount of data, but you don't need the hydrated models. In these scenarios, you can use the "toBase()" method provided by Laravel to save on memory usage π
<?php
// This collection will consist of PHP objects
// and will not include hydrated models
$users = User::toBase()->get();
Laravel Tip π‘: The "value()" Method (β¬οΈ)
Sometimes, you only need a single value instead of the entire row. Laravel comes with the "value()" method, which return the value of the column directly π
<?php
$email = DB::table('users')->where('name', 'John')->value('email');
dd($email); // [email protected]
Laravel Tip π‘: The "whereAll" and "whereAny" Methods (β¬οΈ)
Laravel v10.47.0 has just been released, featuring four new methods: "whereAll," "whereAny," "orWhereAll," and "orWhereAny." These methods allow you to compare a value against multiple columns π
<?php
$search = 'ous%';
// Instead of this
User::query()
->where(function($query) use ($search) {
$query
->where('first_name', 'LIKE', $search)
->where('last_name', 'LIKE', $search);
})
->get();
// You can now do this
User::query()
->whereAll(['first_name', 'last_name'], 'LIKE', $search)
->get();
User::query()
->whereAny(['first_name', 'last_name'], 'LIKE', $search)
->get();
// Which results in the following queries
// select * from `users` where (`first_name` LIKE 'ous%' and `last_name` LIKE 'ous%')
// select * from `users` where (`first_name` LIKE 'ous%' or `last_name` LIKE 'ous%')
// You can also use "orWhereAll" and "orWhereAny".
Laravel Tip π‘: Limit Eager Loaded Relationships (β¬οΈ)
In Laravel versions 10 and below, we couldn't limit eager loaded relationships natively. Well, guess what? In Laravel 11, we can! π
<?php
User::with([
'posts' => fn ($query) => $query->limit(5)
])->paginate();
Laravel Tip π‘: Define Casts as a Method (β¬οΈ)
In Laravel versions 10 and below, we had to define casts as properties which made it a bit messy to pass arguments. In Laravel 11, we can define casts as a method! π
<?php
namespace App\Models;
use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// Laravel β€ 10 π
protected $casts = [
'statuses' => AsEnumCollection::class.':'.ServerStatus::class,
];
// Laravel 11 π
protected function casts(): array
{
return [
'statuses' => AsEnumCollection::of(ServerStatus::class),
];
}
}
Laravel Tip π‘: The "withExists" Method (β¬οΈ)
Did you know that Laravel ships with a method called withExists
which allows you to check if a model has a relationship or not? This is really helpful for conditional logics π
<?php
$user = User::query()
->withExists('posts as is_author')
->get();
/*
select
`users`.*,
exists (select * from `posts` where `users`.`id` = `posts`.`user_id`) as `is_author`
from `users`
*/
$user->is_author; // Will be either true or false.
Laravel Tip π‘: The "whereKey" Method (β¬οΈ)
Did you know that Laravel ships with the "whereKey" method? It makes your "where in" statements more readable, and well, you don't have to remember the name of the primary key π
<?php
// π Instead of doing this
Post::whereIn('id', [1,2,3])->get();
Post::whereNotIn('id', [1,2,3])->get();
// π You can do this
Post::whereKey([1,2,3])->get();
Post::whereKeyNot([1,2,3])->get();
Laravel Tip π‘: Avoid Columns Ambiguity (β¬οΈ)
I'm sure we've all encountered column name ambiguity when building queries at least once. To avoid that, you can use the "qualifyColumn" method, which prefixes the column with the table name π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Team extends Model
{
use HasFactory;
protected $fillable = [
'name',
];
public function servers(): HasMany
{
// Use qualifyColumn() to avoid collision
// with the 'name' column on the Team model
return $this->hasMany(Server::class)->orderBy(
(new Server)->qualifyColumn('name')
);
}
}
Laravel Tip π‘: The "doesntHave" Method (β¬οΈ)
Sometimes, you may want to retrieve all the models that do not have a relationship. While you can achieve this using a query builder, Laravel already ships with the "doesntHave" method, which reads so well π
<?php
use App\Models\Post;
// Posts that do not have the "comments" relationship
$posts = Post::doesntHave('comments')->get();
/*
select *
from `posts`
where
not exists (
select *
from comments
where
`posts`.`id` = `comments`.`post_id`
)
*/
Laravel Tip π‘: Mute All Model Events (β¬οΈ)
Sometimes you may want to mute all events fired by a model. Laravel ships with the "withoutEvents" method that accomplishes exactly that π
<?php
use App\Models\User;
// This will mute all model events π€
$user = User::withoutEvents(function () {
User::findOrFail(1)->delete();
return User::find(2);
});
Laravel Tip π‘: Make use of "unguard" (β¬οΈ)
Did you know that Laravel allows you to unguard a model? While you can still use the forceFill method, this approach enables you to unguard multiple models at once, which is really useful for seeding the database π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
protected $fillable = ['name'];
}
Model::unguard();
// You can have *multiple* models unguarded
Flight::create([
'name' => 'flight',
'not_in_fillable' => true,
]);
Model::reguard();
Laravel Tip π‘: Customize the pivot Attribute Name (β¬οΈ)
We all use many-to-many relationships. In those cases, the intermediate table is accessed via the pivot attribute. Renaming it to something more expressive can make the code more readable π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class User extends Model
{
public function podcasts(): BelongsToMany
{
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();
}
}
$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
// Instead of $podcast->pivot->created_at
echo $podcast->subscription->created_at;
}
Laravel Tip π‘: Increment and Decrement Methods (β¬οΈ)
Sometimes we need to update a value by incrementing or decrementing it. Usually, we would write a query to achieve this, but Laravel comes with elegant methods to do so π
<?php
// Increment votes by 1
DB::table('users')->increment('votes');
// Increment votes by 5
DB::table('users')->increment('votes', 5);
// Decrement votes by 1
DB::table('users')->decrement('votes');
// Decrement votes by 5
DB::table('users')->decrement('votes', 5);
// Increment votes by 1 and set the name to John
DB::table('users')->increment('votes', 1, ['name' => 'John']);
// You can increment multiple columns at once
DB::table('users')->incrementEach([
'votes' => 5, // Will increment votes by 5
'balance' => 100, // Will increment balance by 100
]);
// You can also use them with Eloquent
User::query()->incrementEach([
'votes' => 5, // Will increment votes by 5
'balance' => 100 // Will increment balance by 100
]);
Laravel Tip π‘: Check if the Value of a Given Model Key Has Changed (β¬οΈ)
Sometimes, we wish to check if the value of a given model key has been affected by a change or not. Laravel ships with the "originalIsEquivalent()" method to do exactly that π
<?php
$user = User::firstOrFail(); // ['name' => 'old']
$user->name = 'old'; // Keep the old value
$user->originalIsEquivalent('name'); // true
$user->name = 'new'; // Change the value
$user->originalIsEquivalent('name'); // false
Laravel Tip π‘: The "getOrPut" Method (β¬οΈ)
When using collections, sometimes we want to retrieve the value of an existing key but insert it if it does not exist. While this can be achieved using the "get" and "put" methods, Laravel offers a handy method called "getOrPut" that does the same π
$collection = collect(['price' => 100]);
// Check and set value if not exists
if (!$collection->has('name')) {
$collection->put('name', 'Desk');
}
$value = $collection->get('name');
// Or use getOrPut() for the same operation
$value = $collection->getOrPut('name', 'Desk');
Laravel Tip π‘: Use the Higher Order "orWhere" Method (β¬οΈ)
Laravel supports "Higher Order Messages" with collections, which are cool shortcuts that we use. But did you know that you can make use of them when writing eloquent queries? π
// tip-100.php
<?php
// Instead of this π«
User::popular()->orWhere(function (Builder $query) {
$query->active();
})->get()
// You can do this π
User::popular()->orWhere->active()->get();
Laravel Tip π‘: Faster Queries with "whereIntegerInRaw" (β¬οΈ)
When using a whereIn query with non-user input, opt for whereIntegerInRaw. This speeds up your query by skipping PDO bindings and Laravel's security measures against SQL injection π
<?php
// Instead of using whereIn()
Product::whereIn('id', range(1, 10000))->get();
// Use WhereIntegerInRaw()
Product::whereIntegerInRaw('id', range(1, 10000))->get();
Laravel Tip π‘: The "upsert" Method (β¬οΈ)
Sometimes you may wish to update a bunch of records or create them if they do not exist. Laravel ships with a cool method "upsert" to do exactly that π
<?php
/*
This will update the price of all the records that match
the given departure and destination or create them
*/
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], uniqueBy: ['departure', 'destination'], update: ['price']);
Laravel Tip π‘: No timestamp columns (β¬οΈ)
Sometimes, your table might not have the "created_at" and "updated_at" columns. You can instruct Laravel not to update them by setting "timestamps" to false π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
}
Laravel Tip π‘: The "simplePaginate" Method (β¬οΈ)
Sometimes, when paginating your models, you may only need a "next" and "previous" buttons. For this, you can use the "simplePaginate" method instead of the regular paginate π
<?php
$users = User::paginate(15);
/*
Will result in the following keys
[
"current_page",
"data",
"first_page_url",
"from",
"last_page",
"last_page_url",
"links",
"next_page_url",
"path",
"per_page",
"prev_page_url",
"to",
"total",
];
*/
$users = User::simplePaginate(15);
/*
Will result in the following keys
[
"current_page",
"data",
"first_page_url",
"from",
"next_page_url",
"path",
"per_page",
"prev_page_url",
"to",
];
*/
Laravel Tip π‘: The "doesntExist" Method (β¬οΈ)
Sometimes you may want to check if certain records do not exist in the database. While checking the count or using the exists() method can do the trick, Laravel ships with the "doesntExist" method to do it elegantly π
// Image 5: tip-116.php
<?php
// This is okay π
if (User::count() === 0) {
}
// This is good π
if (! User::exists()) {
}
// This is better π
if (User::doesntExist()) {
}
Laravel Tip π‘: Clone Your Queries (β¬οΈ)
Sometimes you may need to reuse the same base query for multiple filtering. Laravel ships with a "clone" method to do exactly that π
<?php
// Base query, common conditions
$query = User::query()->where('created_at', '<', now()->subMonths(3));
$verified_users = $query->clone()->whereNotNull('email_verified_at')->get();
// This can be customized further if needed
$unverified_users = $query->clone()->whereNull('email_verified_at')->get();
Laravel Tip π‘: Hide Columns On The Fly (β¬οΈ)
Sometimes, you may want to hide model attributes that were not defined in the "hidden" array. Laravel allows you to do this on the fly using the "makeHidden" method π
<?php
$users = User::all()->makeHidden(['address', 'phone_number']);
Laravel Tip π‘: Disable Global Scopes (β¬οΈ)
Laravel allows you to apply global scopes to your models, but sometimes you may wish to disable them for a specific query. You can do this by chaining the "withoutGlobalScope" method π
<?php
// Remove all of the global scopes
User::withoutGlobalScopes()->get();
// Remove a single global scope
User::withoutGlobalScope(FirstScope::class)->get();
// Remove some of the global scopes
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
Laravel Tip π‘: Hash Passwords Automatically (β¬οΈ)
When creating users, we often use the Hash facade, but did you know that Laravel comes with a "hashed" cast that will automatically hash your user's password? π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected function casts(): array
{
return [
'password' => 'hashed',
];
}
}
$user = User::create([
// ...
'password' => 'password', // Instead of Hash::make('password')
]);
Laravel Tip π‘: The "firstOr" Laravel (β¬οΈ)
Sometimes, you may want to execute some actions when no record is found, beyond just creating a new instance. The "firstOr" method allows you to do exactly that π
<?php
$user = User::where('email', $request->input('email'))->firstOr(function () {
// Execute some logic
// Create and return a new user
return User::create([
// ...
]);
});
Laravel Tip π‘: The "latest" and "oldest" Methods (β¬οΈ)
We often order models in ascending or descending order using the "orderBy" method. But did you know that Laravel comes with two methods, "latest" and "oldest," that do exactly that? π
<?php
// Instead of this π₯±
User::orderBy('created_at', 'desc')->get();
User::orderBy('created_at', 'asc')->get();
// Do this π
User::latest()->get();
User::oldest()->get();
// You can specify keys other than created_at
User::latest('id')->get();
User::oldest('id')->get();
Laravel Tip π‘: Insert Or Ignore (β¬οΈ)
Sometimes, you might want to ignore errors when inserting data. Laravel comes with the "insertOrIgnore" method that does exactly that π
<?php
DB::table('users')->insertOrIgnore([
['id' => 1, 'email' => '[email protected]'],
['id' => 2, 'email' => '[email protected]'],
]);
// insert ignore into `users` (`email`, `id`) values (?, ?), (?, ?)
Laravel Tip π‘: Find Many (β¬οΈ)
Did you know that you can pass multiple IDs to the find()
method? Laravel also ships with a slightly more readable method, findMany()
, which does the same thing! π
<?php
// Instead of this
$users = User::query()->whereIn('id', [1, 2, 3])->get();
// Do this
$users = User::find([1, 2, 3]);
// Even better, find() calls findMany() internally, so skip it and be expli
$users = User::findMany([1, 2, 3]);
Laravel Tip π‘: Check If Your Model Has Changed Since Last Retrieval (β¬οΈ)
Did you know Laravel ships with the isDirty()
method, which allows you to check if one or more attributes have changed since the last time you retrieved the model? π
<?php
use App\Models\User;
$user = User::create([
'first_name' => 'John',
'last_name' => 'Doe',
'age' => 20,
]);
$user->age = 21;
$user->isDirty(); // true
$user->isDirty('age'); // true
$user->isDirty('first_name'); // false
$user->isDirty(['first_name', 'age']); // true
$user->isDirty(['first_name', 'last_name']); // false
$user->save();
$user->isDirty(); // false
Laravel Tip π‘: Customize the Default Timestamp Columns (β¬οΈ)
Sometimes, you might need to customize the default timestamp columns, or you might already have an old table for which you are creating a model. Luckily, it is super simple to do so π
<?php
class Flight extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
}
Laravel Tip π‘: Prevent Filling Unfillable Attributes (β¬οΈ)
Did you know you can configure Laravel to throw an exception when attempting to fill an unfillable attribute? This is helpful during development to catch missed or forgotten attributes before getting to prod π
<?php
use Illuminate\Database\Eloquent\Model;
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
Laravel Tip π‘: Create New Records or Update Existing Ones (β¬οΈ)
We've all been in a situation where we want to check if a record exists so we can update it, or create it if it does not. Laravel ships with the updateOrCreate
method to do exactly that π
<?php
$flight = Flight::updateOrCreate(
// If we can't find a flight with the following criteria,
['departure' => 'Oakland', 'destination' => 'San Diego'],
// we will create it with the following data
['price' => 99, 'discounted' => 1]
);
Laravel Tip π‘: Delete (Destroy) Records (β¬οΈ)
Did you know that Laravel ships with the destroy
method, which allows you to delete records by their primary key? π
<?php
// Instead of this π’
Flight::find(1)->delete()
// Do this π
Flight::destroy(1);
Flight::destroy(1, 2, 3); // works with variadic arguments
Flight::destroy([1, 2, 3]); // arrays
Flight::destroy(collect([1, 2, 3])); // and also collections!
Laravel Tip π‘: Cast Values On The Fly (β¬οΈ)
Sometimes you may need to apply casts while executing a query. Luckily, you can do that on the fly with the "withCasts" method that Laravel ships with π
<?php
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id')
])->withCasts([
// Casting the raw string 'last_posted_at' to a datetime
'last_posted_at' => 'datetime'
])->get();
Laravel Tip π‘: The "valueOrFail" Method (β¬οΈ)
We often use the "firstOrFail" method to get a single value from the resulting model. Did you know that Laravel ships with the "valueOrFail" method, which allows you to do exactly that? π
<?php
// Instead of this
$flight = Flight::where('legs', '>', 3)->firstOrFail();
$flightName = $flight->name;
// Do this
$flightName = Flight::where('legs', '>', 3)->valueOrFail('name');
Laravel Tip π‘: The "firstWhere" Method (β¬οΈ)
We often need to get the first record matching a where statement. While "where()" combined with "first()" does the job, Laravel ships with a shortcut "firstWhere()" to do exactly that π
<?php
// Instead of this π
$user = User::query()->where('name', 'john')->first();
// Do this π
$user = User::query()->firstWhere('name', 'john');
Laravel Tip π‘: Prevent N+1 Issues (β¬οΈ)
Eager loading can significantly improve performance. Use the "preventLazyLoading" method to ensure all relationships are eager-loaded during development and customize its behavior for violations π
<?php
use Illuminate\Database\Eloquent\Model;
// In your AppServiceProvider
public function boot(): void
{
// This ensures lazy loading is prevented in your dev environment
Model::preventLazyLoading(! $this->app->isProduction());
// You can customize the behavior for lazy loading violations
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
$class = $model::class;
info("Attempted to lazy load [{$relation}] on model [{$class}].");
});
}
Laravel Tip π‘: Check if valid JSON (β¬οΈ)
We often need to check if a given string is valid JSON. Laravel provides an elegant method, "isJson", to help you with this. It uses the new "json_validate" function in PHP 8.3, and "json_decode" for earlier versions π
<?php
use Illuminate\Support\Str;
// The cool thing is, on the day you upgrade to PHP 8.3, if you haven't already,
// you will automatically be using the new "json_validate()" function
// instead of "json_decode()"
Str::isJson('[1,2,3]'); // true
Str::isJson('{"first": "John", "last": "Doe"}'); // true
Str::isJson('{first: "John", last: "Doe"}'); // false
Laravel Tip π‘: Count words occurances (β¬οΈ)
Ever needed to count the occurrences of a word in a sentence? Laravel ships with the "substrCount" method to do exactly that π
<?php
use Illuminate\Support\Str;
Str::substrCount('My name is Bond, James Bond', 'Bond'); // 2
Laravel Tip π‘: Shortcuts for Dropping Columns (β¬οΈ)
Need to drop some framework-specific columns? You donβt have to specify them manually, Laravel provides shortcuts to do exactly that π
<?php
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['created_at', 'updated_at']);
$table->dropTimestamps();
$table->dropColumn(['deleted_at']);
$table->dropSoftDeletes();
$table->dropColumn(['remember_token']);
$table->dropRememberToken();
$table->dropColumn(['morphable_id', 'morphable_type']);
$table->dropMorphs('morphable');
});
Laravel Tip π‘: Invisible Columns (β¬οΈ)
If you are using MySQL/MariaDB as your database, you can leverage invisible columns. These columns remain hidden in 'SELECT * ' statements, which is perfect for handling sensitive information and precomputed data π
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// Note that this works for MariaDB/MySQL
Schema::table('users', function (Blueprint $table) {
// Useful for storing secrets
$table->string('secret')->nullable()->invisible();
// Or for precomputed columns
$table->string('posts_count')->default(0)->invisible();
});
// The attribute is not even present on the Eloquent object
$user = User::first();
$user->secret; // null
// You have to explicitly select the column
$user = User::select('secret')->first();
$user->secret; // SUEGQs8klF30CLSi
Laravel Tip π‘: Generated Columns (β¬οΈ)
Did you know that Laravel can handle generated columns in migrations out of the box? No need to write raw SQL in your migration to create these columns π
<?php
return new class extends Migration
{
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
// ...
$table->decimal('unit_price');
$table->integer('quantity');
// Works on MariaDB / MySQL / PostgreSQL / SQLite
$table->decimal('full_price')->storedAs('unit_price * quantity');
// ...
});
}
};
Laravel Tip π‘: Disable Model Events When Seeding (β¬οΈ)
In most cases, when seeding the database, you don't need to fire model events. You can use the "WithoutModelEvents" trait to mute those events, making your seeders slightly faster π
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents; // Will mute all model events
public function run(): void
{
$this->call([
UserSeeder::class,
PostSeeder::class,
CommentSeeder::class,
]);
}
}
Laravel Tip π‘: Use Default Models (β¬οΈ)
When working with "hasOne" or "belongsTo" relationships, we often check whether they are nullable before accessing their properties. In such cases, you can use default models to ensure you never get null values π
<?php
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
// Now, instead of checking this every time
Post::first()->user->name ?? 'Guest Author'; // 'Guest Author'
// You can be confident that when a user is null, the default will be used
Post::first()->user->name; // 'Guest Author'
Laravel Tip π‘: Permanently Delete Soft-Deleted Models (β¬οΈ)
Sometimes you may want to permanently remove soft-deleted models. You can use "forceDelete()" for that, or the new "forceDestroy()" method introduced in Laravel v11.21 π
<?php
// Laravel < v11.20
$flight = Flight::find(1);
$flight->forceDelete();
// Laravel v11.21
Flight::forceDestroy(1); // You can also pass an array of IDs
Laravel Tip π‘: Get Only Trashed Records (β¬οΈ)
When working with soft-deleted models, you may need to get only the trashed records. While you can manually filter the query using the "deleted_at" column, there's an "onlyTrashed()" method to do exactly that π
<?php
// Instead of manually specifying 'deleted_at'
$trashedUsers = User::query()->whereNotNull('deleted_at')->get();
// You can use onlyTrashed(), which is both easier and more readable
$trashedUsers = User::query()->onlyTrashed()->get();
Laravel Tip π‘: Add Multiple Columns After Another (β¬οΈ)
Did you know that you can add multiple columns after a specific column using the "after" method? π
<?php
Schema::table('users', function (Blueprint $table) {
$table->after('password', function (Blueprint $table) {
$table->string('address_line1');
$table->string('address_line2');
$table->string('city');
});
});
Laravel Tip π‘: Cache Accessor Result (β¬οΈ)
When working with accessors, objects are cached by default. However, if you have a computationally intensive accessor for a primitive value, like a string, you can enable caching for it using the "shouldCache()" method π
<?php
protected function hash(): Attribute
{
return Attribute::make(
get: fn (string $value) => bcrypt(gzuncompress($value)),
)->shouldCache();
}
Laravel Tip π‘: The "whereLike" method (β¬οΈ)
We often use "where like" statements in our apps. Did you know Laravel ships with a "whereLike" method, which takes it a step further by allowing the like statement to be case insensitive? π
<?php
// Instead of this
User::query()->where('name', 'like', 'Jo%')->get();
// You can do this
User::query()->whereLike('name', 'Jo%')->get();
// You can even specify whether it should be case sensitive
User::query()->whereLike('name', 'Jo%', caseSensitive: true)->get();
// Query: select * from `users` where `name` like binary 'Jo%'
Laravel Tip π‘: Using Multiple IDs and Specific Columns with "find" (β¬οΈ)
We often use "find()", but did you know you can pass an array of IDs and also select specific columns? π
<?php
// You can pass an array of ids, an select specific columns
User::find(id: [1, 2], columns: ['email']);
// select `email` from `users` where `users`.`id` in (1, 2)
Laravel Tip π‘: Load Relationship Count on the Fly (β¬οΈ)
When working with models, you might need the count of a relationship. If you forget to eager load it, you can always use "loadCount" to load the count on the fly π
<?php
$user = User::first();
// Load the count of related posts on the fly
$user->loadCount('posts');
// You can even constraint it
$user->loadCount('posts', fn(Builder $query) => $query->whereNull('published'));
// Access the count via <relationship>_count
$user->posts_count
Laravel Tip π‘: Move Column to First Position (β¬οΈ)
Note
Altering the structure of a populated table might cause downtime, depending on the number of records it has.
Did you know you can move a column to the first position in your table, even if it was added later on? Use the "first()" method to do that π
<?php
Schema::table('posts', function (Blueprint $table) {
$table->string('uuid')->first();
});
Laravel Tip π‘: Latest of Many (β¬οΈ)
Have you ever needed to get the latest record from a one to many relationship? While you can use subqueries to do this, Laravel already ship with the "latestOfMany" method to do exactly that π
<?php
class User extends Model
{
public function latestOrder()
{
return $this->hasOne(Order::class)->latestOfMany('created_at');
}
}
// You can now get the latest order
$latestOrder = $user->latestOrder;
// It can also be eager loaded
$users = User::with('latestOrder')->get();
Laravel Tip π‘: A Cleaner Eager Loading Syntax (β¬οΈ)
Sometimes, when need to eager load nested relationships, and for that we the use dot notation. But did you know you can also pass nested arrays? π
<?php
// Instead of this
$books = Book::with([
'author.contacts',
'author.publisher'
])->get();
// You can pass a nested array
$books = Book::with([
'author' => [
'contacts',
'publisher'
]
])->get();
Laravel Tip π‘: The "insertGetId" Method (β¬οΈ)
Have you ever found yourself needing the ID of a newly inserted row? Laravel ships with the "insertGetId" method to do exactly that π
<?php
$id = DB::table('users')->insertGetId(
['email' => '[email protected]', 'votes' => 0]
);
dd($id); // The ID of the newly inserted row
Laravel Tip π‘: The "ddRawSql" Method (β¬οΈ)
When debugging queries, we often use "dd()" or "toSql()", but did you know you can use "ddRawSql" to get the raw SQL with all bindings substituted? π
<?php
DB::table('users')->where('votes', '>', 100)->ddRawSql();
// "select * from `users` where `votes` > 100"
Laravel Tip π‘: Avoid Duplicate Queries (β¬οΈ)
We often eager load relationships manually using the "load" method. While this works, it can result in duplicate queries when the relationship is already loaded. This can be avoided by using the 'loadMissing' method π
<?php
// If the relationships are already loaded, this will result in duplicate queries
$users->load(['comments', 'posts']);
// This will not query the DB if the relationships are already there
$users->loadMissing(['comments', 'posts']);
Laravel Tip π‘: The New "CollectedBy" Attribute (β¬οΈ)
As of Laravel v11.28, instead of overriding the "newCollection" method, you can now use the new "CollectedBy" attribute to specify a custom collection for your model π
<?php
use Illuminate\Database\Eloquent\Attributes\CollectedBy;
#[CollectedBy(PostCollection::class)]
class Post
{
public function newCollection(array $models = [])
{
return new PostCollection($models);
}
}
Laravel Tip π‘: Aggregate Functions (β¬οΈ)
We often use "withCount" when working with relationships, but did you know other aggregate functions are available out of the box? For example, you can also use "sum", "min", and "max" functions π
<?php
Post::withSum('comments', 'votes')->first(); // $post->comments_sum_votes
Post::withMax('comments', 'votes')->first(); // $post->comments_max_votes
Post::withAvg('comments', 'votes')->first(); // $post->comments_avg_votes
Post::withMin('comments', 'votes')->first(); // $post->comments_min_votes
Post::withExists('comments')->first(); // $post->comments_exists_votes
Laravel Tip π‘: The "toggle" method (β¬οΈ)
At some point, we all needed to toggle a value, for example, a like feature that switches between states. While you can do it manually, Laravel ships with a "toggle" method to do exactly that π
<?php
// Instead of this π
if ($user->likes()->where('tweet_id', $tweet->id)->exists()) {
$user->likes()->detach($tweet->id);
} else {
$user->likes()->attach($tweet->id);
}
// You can do this π₯
$user->likes()->toggle($tweet->id);
// It also works with multiple IDs
$user->products()->toggle([1, 2, 3]);
Laravel Tip π‘: Get the Full Query Log (β¬οΈ)
Ever needed to dump the full query log executed in a method? You can enable the query log at the very beginning and dump it at the end by using "enableQueryLog" and "getRawQueryLog" π
<?php
use Illuminate\Support\Facades\DB;
DB::enableQueryLog();
User::whereNotNull('email_verified_at')->get();
DB::getRawQueryLog();
// select * from `users` where `email_verified_at` is not null
// time: 0.33
Laravel Tip π‘: The New "rawColumn" Method (β¬οΈ)
Laravel v11.32 introduces a new "rawColumn" method. Now, instead of having to use a DB statement when the grammar does not support updating or creating the column, you can use the "rawColumn" method π
<?php
new class extends Migration {
public function up()
{
// Instead of this
Schema::create('posts', function (Blueprint $table) {
$table->id();
});
DB::statement('alter table `posts` add `legacy_boolean` int(1) default 0 not null');
Schema::table('posts', function (Blueprint $table) {
$table->index(['id', 'legacy_boolean']);
});
// You can now do this
Schema::create('table', function (Blueprint $table) {
$table->id();
$table->rawColumn('legacy_boolean', 'int(1)')->default(0);
$table->index(['id', 'legacy_boolean']);
});
}
};
Laravel Tip π‘: Explain Eloquent Queries (β¬οΈ)
Have you ever needed to run an EXPLAIN on an Eloquent query to check if an index is being used? While you could manually extract the raw query and run EXPLAIN on it, you can just chain the "explain" method to do exactly that π
<?php
User::query()
->where('email', '[email protected]')
->explain()
->dd();
array:1 [
0 => {#3368
+"id": 1
+"select_type": "SIMPLE"
+"table": null
+"partitions": null
+"type": null
+"possible_keys": null
+"key": null
+"key_len": null
+"ref": null
+"rows": null
+"filtered": null
+"Extra": "no matching row in const table"
}
]
Laravel Tip π‘: The "firstOrNew" Method (β¬οΈ)
Sometimes you may want to check if a model exists, and if not, instantiate it without saving it to the database right away. Laravel ships with the "firstOrNew" to do exactly that π
<?php
use App\Models\User;
// Find the user by email, or save it to the database if it doesn't exist
User::firstOrCreate(['email' => '[email protected]']);
// Find the user by email, or instantiate a new User instance without saving it
User::firstOrNew(['email' => '[email protected]']);
// Do something with $user
// Save it to the database
$user->save();
Laravel Tip π‘: The "withWhereHas" Method (β¬οΈ)
Have you ever needed to eager load a relationship but also constrain it with relationship existence? While you can do that manually with 2 methods, Laravel ships with the "withWhereHas" method to do exactly that π
// Instead of this
User::query()
->whereHas('posts', fn (Builder $query) => $query->where('featured', true))
->with(['posts' => fn (Builder $query) => $query->where('featured', true)])
->get();
// You can simply use withWhereHas π₯
User::query()
->withWhereHas('posts', fn (Builder $query) => $query->where('featured', true))
->get();
// This will retrieve all users that meet the condition, along with only their featured posts.
Laravel Tip π‘: Update Pivot Columns (β¬οΈ)
Have you ever needed to update a pivot column? While you can manually handle this with the query builder, Laravel ships with the "updateExistingPivot" method to do exactly that π
<?php
$user = User::first();
$roleId = Role::value('id');
$user->roles()->updateExistingPivot($roleId, [
'active' => false,
]);
// UPDATE `role_user` SET `active` = 0 WHERE `role_user`.`user_id` = 1 AND `role_id` IN (1)
Laravel Tip π‘: Prevent Accessing Missing Attributes (β¬οΈ)
Did you know you can configure Laravel to throw an exception when attempting to access a missing attribute? This is useful during development to catch typos or changes in the table structure π
<?php
use Illuminate\Database\Eloquent\Model;
Model::preventAccessingMissingAttributes(! app()->isProduction());
$user = User::select('name')->first();
$user->email // throws a `MissingAttributeException` exception