Объем по умолчанию для моделей Eloquent?

Вот пример таблицы базы данных (users):

id - int(11) auto_increment
name - varchar(100)
banned - int(1)

Столбец banned — это логическое значение, которое по умолчанию равно 0 (false). Если пользователь был забанен, значение равно 1.

Я хочу исключить всех заблокированных пользователей из всех запросов по умолчанию. Я мог бы создать область запроса и затем использовать ее везде. Тем не менее, я бы предпочел просто включить эту проверку по умолчанию.

Я также мог бы создать собственный newQuery-метод, например:

// Inside User-model, which extends Eloquent
public function newQuery($excludeDeleted = true)
{
    $builder = parent::newQuery($exludeDeleted);
    $builder->where('banned', '=', '0');
    return $builder;
}

Однако таким образом я не смогу отключить это поведение. Я мог бы захотеть увидеть заблокированных пользователей в моей личной панели администратора, но не смог бы, так как это ограничение будет применяться к любому запросу, сделанному через Eloquent.

Любая идея о том, как решить эту проблему?


person Martti Laine    schedule 16.02.2014    source источник
comment
Насколько мне известно, переопределение newQuery() действительно единственный способ для этого. Я оказался в похожей ситуации, и я подумал о создании scopeBase(), который является личным соглашением (а не о Laravel!) и прилагал усилия, чтобы не забывать вызывать Model::base()->get() и т. д. всякий раз, когда мне нужно получить доступ к моделям . Это далеко не идеально, но спасает от переопределения newQuery(). Думаю, оба плохи по-своему. Однако ответ JohnTaa кажется вам подходящим.   -  person alexrussell    schedule 16.02.2014
comment
Покопался. Вы можете применять глобальные области вот так. Пример, который я привожу, относится к многоразовым областям; вам это может не понадобиться, так как только пользователи могут быть забанены.   -  person mpen    schedule 25.04.2014


Ответы (6)


Laravel предоставляет глобальные области видимости именно для этой цели. Из документов:

class AgeScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        return $builder->where('age', '>', 200);
    }
}

class User extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new AgeScope);
    }
}
person Ben Claar    schedule 19.04.2019
comment
Лучший ответ! - person Jakub; 04.02.2021

Вместо того, чтобы создавать отдельный класс, вы реализуете класс Model

<?php

namespace App;

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

class User extends Model
{
    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope('banned', function (Builder $builder) {
            $builder->where('banned', 0);
        });
    }
}
person sudin    schedule 21.08.2020

Отвечаю здесь для других, которые могут искать аналогичный ответ.

Вы можете создать глобальную область, чтобы модель использовала глобальную область по умолчанию, а затем использовать withoutGlobalScope, когда вы хотите сделать запрос без применения этой области.

См. документы Laravel: https://laravel.com/docs/5.8/eloquent#global-scopes


Итак, в вашем случае вы должны создать новую глобальную область для запроса пользователей, которые не заблокированы.

<?php

namespace App\Scopes;

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

class ExcludeBannedScope 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('banned', '=', 0);
    }
}

Примените глобальную область действия к вашей модели пользователя.

<?php

namespace App;

use App\Scopes\ExcludeBannedScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new ExcludeBannedScope);
    }
}

Теперь это добавит where banned = 0 ко всем запросам.

В разделе администратора, где вы хотите видеть всех пользователей, включая забаненных, вы можете сделать

User::withoutGlobalScope(\App\Scopes\ExcludeBannedScope::class)->get();
person David Maksimov    schedule 20.01.2020

Это очень похоже на обратимое удаление, но с banned_at вместо deleted_at. Если поведение по умолчанию не показывает забаненных пользователей, я думаю, что более интуитивно понятно явно запрашивать забаненных (например, withTrashed), когда они вам нужны (панель администратора).

person sigmus    schedule 16.02.2014
comment
Мне нужна функциональность, аналогичная мягкому удалению, да. - person Martti Laine; 16.02.2014
comment
@MarttiLaine Я тоже это изучаю. Решение пока не найдено, но это может дать нам подсказку. - person mpen; 25.04.2014

Я настоятельно рекомендую использовать Repository Design Pattern для запросов к БД вместо того, чтобы делать прямые запросы Eloquent в контроллерах и везде.

// Quick example, not tested
class UserRepositoy { // You should create an interface and maybe super class for handling common cases like caching, find(), all() etc

    protected $include_banned = false;
    protected $model;

    public function __construct() // you can use DI here
    {
            $this->model = new User;
    }

    public function setIncludeBanned($bool)
    {
        $this->include_banned = $bool;
    }

    protected function includeBanned($query)
    {
        if ($this->include_banned) {
            return $query->where('banned', 1);
        }
    }

    public function find($id)
    {
        $res = $this->model->where('id', $id);

        $res = $this->includeBanned($res);

        return $res->get();
    }

}

Теперь вы можете инициировать класс репозитория везде, где вам нужны запросы, и у вас есть унифицированный API для вызовов. В Laravel действительно легко распространять небольшие запросы Eloquent здесь и там, но в долгосрочной перспективе это будет очень раздражать, чтобы обновлять/изменять и справляться. Попробуйте поискать в Google Laravel Design Pattern, там много информации и примеров. Сделал мой день пару раз уже.

Этот шаблон также упрощает замену всего Eloquent на что-то другое, если это необходимо.

person trm42    schedule 04.02.2015

Почему бы вам не использовать для этого переменную конфигурации:

public function newQuery($excludeDeleted = true)
{
    $builder = parent::newQuery($exludeDeleted);
    if (Config::get('hide_banned_users', true) !== false) {
        $builder->where('banned', '=', '0');
    }
    return $builder;
}

и меняйте значение конфигурации всякий раз, когда вам нужно увидеть забаненных пользователей.

person JohnTaa    schedule 16.02.2014