Apply the opposite of your Eloquent scope to the Query Builder with a Laravel package

Last week I blogged about a new package that I wrote. It builds upon the magic of Laravel Eloquent, and it quickly gained attention. As I worked on a minor release with some enhancements and shortcuts, I got an idea for a new feature. It's closely related to last week's package, but I decided to wrap it up in a new package as I didn't want to bloat it.

So here we are, introducing another new package: Laravel Eloquent Where Not. Beautiful name, isn't it? Let me guide you through it.

You've probably been at the point where you've written a scope for your Eloquent Model that uses all kinds of constraints. Where clauses, relationship counts, date comparisons, you name it. I love the way scopes can clean up your code and prevent you from writing the same constraints repeatedly. But you've presumably been also at the point where you want the opposite outcome of a scope. In plain PHP, this would be an easy thing to do. Wrap the statement in parentheses and negate the expression. Quick example:

$onFrontPage = $post->votes > 100 && $post->comments_count > 20;
 
$notOnFrontPage = !($post->votes > 100 && $post->comments_count > 20);

Of course, there are more readable ways to achieve the same result. But how can we apply this to database queries? Let's take a look at the Eloquent model below and focus on the onFrontPage scope:

class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
 
public function comments()
{
return $this->hasMany(Comment::class);
}
 
public function scopeOnFrontPage($query)
{
$query->where('is_public', 1)
->where('votes', '>', 100)
->has('comments', '>=', 20)
->whereHas('user', fn($user) => $user->isAdmin())
->whereYear('published_at', date('Y'));
}
}

Now we can fetch all posts that are suitable for the front page:

$frontPagePosts = Post::onFrontPage()->get();

But what if we want to fetch all posts that didn't make the front page? You can write another scope, sure! But if your business rules change, you need to update at least two scopes.

This new package introduces a whereNot method. This method allows you the flip the scope, or as some would say, invert the scope. This method is incredibly easy to use. You get a $query instance, just like defining scopes, to call any scope or constraint on. This method will apply the opposite of the scope.

$nonFrontPagePosts = Post::whereNot(function($query) {
$query->onFrontPage();
})->get();

It comes with a bunch of shortcuts to make this even more readable:

$nonFrontPagePosts = Post::whereNot('onFrontPage')->get();

You can find the package and all its features and documentation on Github, and I did a live demo on YouTube as well. It's only 14 minutes, and it starts with a brief introduction of the demo application. Lately, I've been doing a weekly session on YouTube with all kinds of Laravel related demos and packages. Please subscribe to my channel for more videos!