Глобальные прицелы 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()));
});