Skip to content

Latest commit

Β 

History

History
1841 lines (1316 loc) Β· 57.4 KB

eloquent-and-database.md

File metadata and controls

1841 lines (1316 loc) Β· 57.4 KB

Eloquent & Database Tips (cd ..)

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