• Час читання ~3 хв
  • 06.03.2023

Глобальні масштаби Laravel чудові, але я не бачу, щоб вони використовувалися багато. Натомість я бачу, що багато місцевих областей використовуються для досягнення того ж самого. При належному впровадженні глобальних масштабів код і безпека були б значно покращені. Дозвольте проілюструвати це простим прикладом.

The local scope way

У нашій кодовій базі ми маємо модель Транзакції, яка зберігає транзакції для наших користувачів. Якщо ми хочемо отримати транзакцію для залогіненого користувача з бази даних, ми могли б зробити це так:

$transactions = Transaction::where('user_id', auth()->id())->get();

Оскільки ми будемо використовувати це багато по всій кодовій базі, було б сенс створити локальну область у моделі транзакції, як це:


namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Transaction extends Model
{
    public function scopeForLoggedInUser($query): void
    {
        $query->where('user_id', auth()->id());
    }
}

За допомогою цієї локальної області ми можемо зробити запит таким чином:

$transactions = Transaction::forLoggedInUser()->get();

Це гарний СУХИЙ (Don't Repeat Yнаш) рефактор, який трохи очищає його.

A question you should ask yourself

Місцеві області чудові, я використовую їх там, де можу, щоб зберегти код СУХИМ. Але я навчився задавати собі це питання, коли створюю локальну сферу застосування: «Чи буде більшість запитів до цієї моделі використовувати цю локальну сферу».

Якщо відповідь негативна, зберігайте локальну сферу застосування. Використовувати його має великий сенс.

When the answer is yes

Це було б моментом при розгляді глобального масштабу. Глобальна область завжди застосовується до всіх запитів певної моделі. Ви можете створити глобальну область, просто створивши клас, який реалізує Illuminate\Database\Eloquent\Scope.

<?php

namespace App\Models\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class TransactionsForLoggedInUserScope implements Scope
{

    public function apply(Builder $builder, Model $model)
    {
        $builder->where('user_id', auth()->id());
    }
}

Після цього ви повинні повідомити свою модель про глобальну сферу дії:

<?php

namespace App\Models;

use App\Models\Scopes\TransactionsForLoggedInUserScope;
use Illuminate\Database\Eloquent\Model;

class Transaction extends Model
{
    

    protected static function booted(): void
    {
        static::addGlobalScope(new TransactionsForLoggedInUserScope());
    }
}

Якщо ви зараз отримаєте всі транзакції, вона поверне лише транзакції зареєстрованого користувача.

Transaction::all();

Removing a global scope

Є кілька сценаріїв, які можна уявити, коли ви хочете створити запит транзакції без застосування глобальної сфери. Наприклад, в огляді адміністратора або для деяких глобальних статистичних обчислень. Це можна зробити, використовуючи метод withoutGlobalScope:

Transaction::withoutGlobalScope(TransactionsForLoggedInUserScope::class)->get();

Anonymous Global Scopes

Існує також спосіб створити глобальну сферу без використання додаткового файлу. Замість того, щоб вказувати на клас TransactionsForLoggedInUserScope, ви можете включити запит так

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Transaction extends Model
{
    

    protected static function booted(): void
    {
        static::addGlobalScope('for_logged_in_users', function (Builder $builder) {
            $builder->where('user_id', auth()->id());
        });
    }
}

:Особисто мені не подобаються анонімні глобальні сфери, оскільки він може роздути вашу модель, якщо запит трохи складний. Щоб бути послідовним протягом усієї кодової бази, я завжди схиляюся до використання глобальних областей зовнішніх файлів, навіть якщо запит такий простий, як у нашому прикладі.

The downside of global scopes

Я не буду вам брехати, якщо ви працюєте над кодовою базою з глобальними прицілами і не знаєте про них, ви можете потрапити в ситуації, коли більше нічого не має сенсу. Це змусило мене поставити під сумнів мій вибір кар'єри в один (дуже поганий) день 😂 . Ви отримаєте абсолютно несподівані результати, повозившись. Якщо це сталося один раз, ви дізналися, що добре мати метод безGlobalScopes у своєму поясі інструментів під час роботи з кодовою базою, з якою ви не зовсім знайомі.

Security

Якщо безпека вашого додатка залежить від глобального масштабу, небезпечно, коли ви або його колега-розробник вносите до нього зміни в майбутньому. Уявіть собі на нашому прикладі, що хтось прибрав би або змінив глобальний масштаб. Тоді всі результати для транзакцій користувача будуть абсолютно помилковими! Ось чому дуже важливо впроваджувати тести для глобальних масштабів, тому цього не може статися. Типовий (PEST) тест для нашого прикладу буде виглядати так:

it("should only get the transactions for the logged-in user", function (){
    $user = User::factory()->create();
    $otherUser = User::factory()->create();

    $transaction_1 = Transaction::factory()->create(['user_id' => $user->id]);
    $transaction_2 = Transaction::factory()->create(['user_id' => $otherUser->id]);

    $this->actingAs($user);
    expect(Transaction::all())->toHaveCount(1)
        ->and($transaction_1->is(Transaction::first()));
});

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297