Laravel Builder Query Scope



라라벨 Query Scope 는 두가지 방식이 존재합니다.

Query Scope 로 쉽게 재사용 가능한 쿼리 제약 조건을 정의할 수 있습니다.

Local Scope

스코프를 정의하려면 모델 메서드 앞에 Scope 키워드를 접두사에 포함하면 됩니다.

아래는 User 모델에 상태(status) 조건을 정의하는 Scope 입니다.

/**
 * Local Scope
 *
 * @param Builder $query
 * @return mixed
 */
public function scopeActive(Builder $query): Builder
{
    return $query->whereStatus(StatusEnum::ACTIVE->value);
}

Local Scope 를 활용하려면 해당 모델에 Method Chaining 방식으로 호출하면 됩니다.

$users = User::query()
            ->active()
            ->get();

scopeActive 스코프는 상태 (status) 가 active 인 데이터만 조회하게 됩니다.

하지만 파라미터로 상태 값을 넘겨 조회하고 싶다고 하면 Dynamic Scope 를 사용하면 됩니다.

/**
 * Local Scope (Dynamic Scope)
 *
 * @param Builder $query
 * @param int $status
 * @return Builder
 */
public function scopeStatus(Builder $query, int $status): Builder
{
    return $query->whereStatus($status);
}

조회 시 Method Chaning 방식은 동일하나 호출 시 파라미터를 포함할 수 있습니다.

$users = User::query()
    ->status(StatusEnum::INACTIVE->value)
    ->get();

Global Scope

모델에서 Global Scope 를 사용한다면 모든 쿼리에 해당 제약 조건을 정의할 수 있습니다.

Artisan 명령어로 Global Scope 파일 생성이 가능합니다.

$ php artisan make:scope ActiveStatusScope

Global Scope 는 위 명령어로 생성한 클래스의 apply 메서드만 구현하면 됩니다.

상태 (status) 가 active 인 데이터만 조회할 수 있도록 제약 조건을 정의합니다.

class ActiveStatusScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('status', StatusEnum::ACTIVE->value);
    }
}

Global Scope 를 사용할 모델에서 스코프를 적용하기 위해서는 booted 메서드를 재정의하고 addGlobalScope 메서드에 생성한 스코프의 인스턴스를 생성합니다.

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

Global ScopeLocal Scope 와 다르게 모든 쿼리에 apply 메서드에 구현한 조건이 사용됩니다.

$users = User::query()
    ->get();

위 쿼리의 결과는 where 절이 포함되어 있습니다.

array:1 [
  0 => array:3 [
    "query" => "select * from `users` where `status` = ?"
    "bindings" => array:1 [
      0 => 1
    ]
    "time" => 1054.94
  ]
]

상황에 따라 Global Scope 사용이 불필요한 경우가 있습니다.

그 경우 모델 사용시에 withoutGlobalScope 메서드에 스코프 클래스 자체를 파라미터로 넘겨주면 됩니다.

$users = User::query()
    ->withoutGlobalScope(ActiveStatusScope::class)
    ->get();

위 쿼리의 결과는 withoutGlobalScope 로 정의한 스코프를 제거했기 때문에 ActiveStatusScope 스코프의 제약 조건을 포함하고 있지 않습니다.

array:1 [
  0 => array:3 [
    "query" => "select * from `users`"
    "bindings" => []
    "time" => 115.29
  ]
]

마침

Laravel Deepdive 저장소에서 사용된 예제 코드를 확인할 수 있습니다.

참고

https://laravel.com/docs/9.x/eloquent#query-scopes