11

Here is a sample database-table (users):

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

The column banned is a boolean value which is 0 (false) by default. If an user has been banned, the value is 1.

I'd like to exclude any banned users from all queries by default. I could create a query scope and then use that everywhere. However, I'd much more prefer simply having that check on by default.

I could also create a newQuery-method of my own, like so:

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

However, this way I wouldn't be able to switch off this behaviour. I might want to see the banned users in my private admin panel, but would not be able to, as this restriction would be applied to any query done via Eloquent.

Any idea on how to solve this problem?

MasterAM
  • 16,283
  • 6
  • 45
  • 66
Martti Laine
  • 12,655
  • 22
  • 68
  • 102
  • As far as I'm aware, overriding `newQuery()` is indeed the only way for this. I have found myself in a similar situation and I've considered creating a scopeBase() which is a personal convention (not a Laravel one!) and putting the effort in to remember to call `Model::base()->get()`, etc. whenever I need to access the models. That's far from ideal, but saves overriding `newQuery()`. I guess both are bad in their own ways. JohnTaa's answer seems like it'd work for you though. – alexrussell Feb 16 '14 at 15:43
  • 2
    Did some digging. You can apply global scopes [like this](https://github.com/laravel/framework/issues/3897#issuecomment-41351443). The example I give is for re-usable scopes; you might not need that as only users can be banned. – mpen Apr 25 '14 at 01:35

6 Answers6

15

Laravel provides Global Scopes for exactly this purpose. From the docs:

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);
    }
}
Ben Claar
  • 3,285
  • 18
  • 33
10

Instead of creating a separate class, you implement on the Model class

<?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);
        });
    }
}
sudin
  • 314
  • 2
  • 11
4

Replying here for others that may be looking for a similar answer.

You can create a global scope, have the model use the global scope by default, then use the withoutGlobalScope when you want to make a query without that scope applied.

See Laravel docs: https://laravel.com/docs/5.8/eloquent#global-scopes


So in your case you would create a new global scope to query users that are not banned.

<?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);
    }
}

Apply the global scope to your User model.

<?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);
    }
}

This will now added the where banned = 0 to all queries.

In your admin section where you want to see all users including banned you can do

User::withoutGlobalScope(\App\Scopes\ExcludeBannedScope::class)->get();
David Maksimov
  • 369
  • 5
  • 17
2

I strongly suggest on using Repository Design Pattern for DB queries instead of doing direct Eloquent queries in controllers and everywhere.

// 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();
    }

}

Now you can instiate the repository class where ever you need queries and you have unified API to the calls. In Laravel it's really easy to spread small Eloquent queries here and there but in the long run it will be really annoying to update/change and cope. Try googling for Laravel Design Pattern and there's lots of information and examples about it. Has made my day couple of times already.

This pattern also makes easier to ditch the whole Eloquent with something else if necessary.

trm42
  • 2,536
  • 2
  • 19
  • 16
1

This sounds a lot like soft deleting, but with banned_at instead of deleted_at. If the default behaviour is not to show the banned users, I think it is more intuitive to explicitly ask for the banned (like withTrashed) when you need them (admin panel).

Martti Laine
  • 12,655
  • 22
  • 68
  • 102
sigmus
  • 2,987
  • 3
  • 23
  • 31
  • I'm after similar functionality as with soft deleting, yes. – Martti Laine Feb 16 '14 at 16:02
  • @MarttiLaine I'm investigating this too. Haven't found a solution yet, but [this](https://github.com/laravel/framework/blob/1181e63a49921612cf441cbad65b345ccabdcb75/src/Illuminate/Database/Eloquent/SoftDeletingTrait.php#L104-L112) might give us a clue. – mpen Apr 24 '14 at 20:52
0

Why don't you use config variable for that:

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

and change the config value whenever you need to see the banned users.

alexrussell
  • 13,856
  • 5
  • 38
  • 49
JohnTaa
  • 2,722
  • 2
  • 15
  • 15