AliAdams's avatar

If I have for example defined scopeActive(), how best should I test if an Eloquent object "is Active"?

Long time lurker, first time poster - hi!

This has been bugging me for a while - hopefully one of you guys will know the best way of doing this!

If I have defined a local scope such as 'active' is there a way to test whether a certain Eloquent Object is active without duplicating the logic used in the definition of the scope?

$active_widgets = Widget::active()->get(); // < using the scopeActive() to pull a list of active widgets is fine

$widget = Widget::find(1);

if( $widget->isActive() ){ // < What I'm trying to achieve
    echo 'Yay!';
} else {
    echo 'Awww...';
}

Ideally I'm looking for the most efficient way to do this without duplicating the code used to define the scope and without making an additional database query like:

 // In Widget class:

public function isActive(){
    return self::active()->where('id',$this->id)->count();
}
0 likes
14 replies
tykus's avatar

What is in the scopeActive method?

YourisActive() method can be replaced with an accessor:

public function getIsActiveAttribute()
{
    // logic to determine if the Widget is active
}
AliAdams's avatar

scopeActive is using laravel's inbuilt local scopes: https://laravel.com/docs/5.4/eloquent#local-scopes

an example would be

 // In Widget class:

public function scopeActive($query){
    return $query->where('active',1);
}

My question is not as much about how to access 'isActive' as an attribute, but rather, how to determine the value of isActive(), given scopeActive, without duplicating the logic in scopeActive.

An example of duplicating the code would be to write something like:

 // In Widget class:

public function isActive(){
    return (bool) $this->active == 1;
}

But that means if I change what 'active' is (eg say a widget becomes inactive if it hasn't been used in 1 year), I now would have to change both the scopeActive AND the isActive definitions which feels like poor code.

(I'm also aware that this is a simplified example - scopes can also have parameters and be significantly more complicated)

tykus's avatar

I know what local scopes are! I meant what is actually inside that method?

EDIT: no; there is no way to do that; one is a query scope, and the other is an accessor. I suppose, you could create a default scope on the model which adds a column to all Eloquent queries on that model and encapsulates what it means to be "active"

AliAdams's avatar

I'm not sure how that affects the solution, but I've updated the comment to provide more detail.

My objective here is to find a solution for all 'scopeX'/'isX' pairs; not just this specific example.

tykus's avatar

In the case of the isActive() method above, it doesn't require a method because you can cast the active property to a boolean using the $casts property on the model. Then you have $widget->active to use in your conditionals.

But this will fall apart whenever the logic of what it means to be active changes, e.g. active == true && subscription_ends > Carbon::today() && ...

AliAdams's avatar

Exactly! So the solution we are looking for has to in some way capture the 'scopeActive()' local scope's logic, indiscriminate of what that logic is.

Any other ideas?

mikefolsom's avatar

I suppose you could use a magic method to intercept and parse the isXXX() call, then generate a collection of objects using the "matching" query scope, and test whether the current object is in that collection…

But that seems way more complicated and much more fragile than just updating the isXXX() method when necessary. :)

Snapey's avatar

I don't think you can really combine them because one is inspecting attributes of a single model, the other is a query determining which models to include in a set.

In one case you want a boolean response, in the other a filter.

AliAdams's avatar

Thanks @mikefolsom and @Snapey for the responses.

I'll have a play with the collections idea Mike and see if that works. I realise it may be more fragile, especially given that I think the filters for collections work a little differently than on database queries, but it's worth a try. Either that or I'll just continue duplicating the definitions....ewww.

I'm surprised there isn't already an in-built version of something like this - there seems so many times I end up looking for it

  • scopePaid / isPaid
  • scopeOverdue / isOverdue
  • scopeArchived / isArchived
  • scopeAdmin / isAdmin
1 like
Snapey's avatar

Its common to have those types of functions, but expecting a single rule to cover both scenarios is the problem.

eg scopePaid is a condition on the database expressed through a where statement

isPaid might be a boolean on a single model instance - but its certainly not a where statement.

Tito1337's avatar

I was also looking to test a scope on an instance, and had a hard time finding a definitive answer to this... In particular when the scope is complex and implementing twice is prone to errors (I have one scope looking through two many2many and that I had to modify twice recently...)

Anyway this is how I fixed it where I really need it :

public function getIsScopeAttribute()
{
    return self::scope()->find($this->id) != null;
}

Obviously you need a primary key ID, or another logic connector. And it does use an additionnal database query (but in my case testing through many2many relationships anyway, it is not a penalty)

It's too bad that Laravel does not implement a magic "isScope" attribute.

Rikipm's avatar

You also can use

public function isActive()
{
    return self::active()->where(self::getKeyName(), $this->getKey())->exists();
}
amit-viacon's avatar

Try this one

public function isScope($scope, ...$attributes)
{
    return static::$scope(...$attributes)->where($this->getKeyName(), $this->getKey())->exists();
}

Use it like

$widget->isScope('active');
2 likes

Please or to participate in this conversation.