i'm using laravel 5.2 for my project I just implemented a local scope (let's use laravel docs example for simplicity - https://laravel.com/docs/master/eloquent#local-scopes )
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include popular users.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
}
I call:
$users = App\User::popular()->get();
And it works like a charm
Is it possibile to call a negation of a scope?
$users = App\User::Notpopular()->get();
Or I have to manually implement the negation of the said scope? If so is there a better way to do it?
Thank you in advance!
I am using this piece of code for "negating" scopes.
public function scopePublished($query, $negate = false) {
return ($negate ? $query->where('published', false) : $query->where('published', true));
}
Now if I simply want published records I call $post->published()...
I want all non-published records I call $post->published(true)...
I might to switch to following, because its simpler to understand:
public function scopePublished($query, $override = true) {
return $query->where('published', $override);
}
published records: $post->published()...
unpublished records: $post->published(false)...
It's not possible (technically it is, but it would require much more code) to create a local scope being negation of another local scope without implementing another scope method. It is however possible to reuse the negated criteria from one scope in another scope.
The following code should do the trick:
public function scopeNotPopular($query) {
$query->whereNotIn($this->getKeyName(), function($q) {
$q->select($this->getKeyName())->from($this->getTable());
$this->scopePopular($q);
});
}
This code uses whereNotIn constraint to apply negated constraint from another scope. It will allow you to reuse constraint logic instead of implementing original and negated versions in 2 different places. While this amount of code seems like quite an overhead with the example scope from the docs, it makes more sense with more complex constraints.
Keep in mind that while it allows code reuse, the SQL query that will be applied might be sub-optimal from performance point of view. It uses a subselect to identify rows that should be excluded from the result set.
I think you need implement manually, because the Laravel won't "negate" automagically.
What you can do is use Dynamic Query Scopes: https://laravel.com/docs/5.0/eloquent#query-scopes
So, in Dynamic Query Scopes, you can pass additional parameters, and one of them could be if must get most popular people, or the less popular.
Here is another approach that is similar to the accepted answer yet I feel can be better for performance since it doesn't use the subquery. In my use case the scope I was negating had multiple complex conditions which I was looking to avoid replicating.
public function scopeNotPopular($query)
{
// whereNot doesn't exist in Laravel so doing similar logic here to avoid replicating popular logic
// whereRaw('TRUE') insures the "AND NOT" on the later WHERE will not be trimmed
// end result: WHERE true AND NOT ([popular logic])
return $query->whereRaw('TRUE')
->where(function ($q) {
$q->popular();
}, null, null, 'AND NOT');
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With