• Время чтения ~1 мин
  • 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();

Это хороший DRY (Don't Repeat Yнаш) рефакторинг, который немного очищает его.

A question you should ask yourself

Локальные области потрясающие, я использую их там, где могу, чтобы сохранить код DRY. Но я научился задавать себе этот вопрос, когда создавал локальную область: «Будет ли большинство запросов для этой модели использовать эту локальную область».

Если ответ отрицательный, сохраните локальную область. В его использовании есть большой смысл.

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