Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Kryptonit3's avatar

Global Query Scope and related model method

I am trying to figure out how to apply a related model method onto a global query scope. Here is what I am working with.

// Models
User
Jobs

// Relationships
User->hasMany('Jobs')
Job->belongsTo('User')

I am using stripe so users have subscriptions and can be checked by calling $user->subscribed(). What I would like to do is when calling the Jobs::all() only show jobs from subscribed users.

From the laracast video on global scopes and the documentation I can see how it can be done with the query builder on $this (the model that is invoking the trait) model, but I want to query it's relationship.

Any ideas?

I see this in the Scope

    /**
     * Apply scope on the query.
     *
     * @param \Illuminate\Database\Eloquent\Builder  $builder
     * @param \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {

Can I somehow use the $model variable to accomplish this?

Like $model->user->subscribed(); ? (tried this, doesn't work).

0 likes
19 replies
Kryptonit3's avatar

@absiddiqueLive While I appreciate your comment, this is not what I am looking for. I have my models and relationships set up just fine. What I would like to be able to do is get all jobs where the user has an active subscription. I was hoping that using a global query scope would be able to help, but I have no idea how I would query a related model in the global query scope itself.

Kryptonit3's avatar

Something like this (this is mixing query builder with eloquent, it doesn't work, just showing you what I'd like to happen)

Jobs::whereHas('user', function($query) {
    $query->subscribed(); // this is an eloquent method for checking a stripe subscription.
})->get();

I am aware I cannot mix eloquent methods with the query builder. Just trying to show you what I am trying to accomplish.

absiddiqueLive's avatar

@Kryptonit3 , try this

UserModel::whereHas('login', function ($query) use ($type){
            $query->where('type', '=', $type);
        })->with('login')->get();
Kryptonit3's avatar

This is not what I am looking for. I am looking for a way to implement a global query scope on the Jobs model so when I call Jobs::all() it only retrieves records from users with an active stripe subscription.

pmall's avatar

@Kryptonit3 The problem is suscribed() isn't a query scope. I don't know stripe/cashier, isn't there something in the user table thats indicates they are subscribed?

How do this method know if a user is subscribed or not?

Kryptonit3's avatar

@pmall it is a method that is injected with a trait. It returns a boolean.

Here is the flow of it $this refers to the User object.

    /**
     * Determine if the entity has an active subscription.
     *
     * @return bool
     */
    public function subscribed()
    {
        if ($this->requiresCardUpFront()) {
            return $this->stripeIsActive() || $this->onGracePeriod();
        } else {
            return $this->stripeIsActive() || $this->onTrial() || $this->onGracePeriod();
        }
    }

Here is the full file for all references - https://github.com/laravel/cashier/blob/5.0/src/Laravel/Cashier/Billable.php#L198-L210

pmall's avatar

You cant do it with global scopes. Use some king of logic to filter the user collection.

$subscribed_users = $users->filter(function ($user) {

    return $user->subscribed();

});

You can put this in a custom users collection.

Kryptonit3's avatar

This looks like it will work

    /**
     * Apply scope on the query.
     *
     * @param \Illuminate\Database\Eloquent\Builder  $builder
     * @param \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('active', true)->whereHas('user', function($q) {
            $q->where('account_type', 'company')
              ->where('stripe_active', true);
        })->orWhereHas('user', function($q) {
            $q->where('account_type', 'company')
              ->whereNotNull('subscription_ends_at')
              ->where('subscription_ends_at', '>', Carbon::now());
        })->orWhereHas('user', function($q) {
            $q->where('account_type', 'company')
              ->whereNotNull('trial_ends_at')
              ->where('trial_ends_at', '>', Carbon::today());
        });
    }

Anyone want to help me write the remove() function for the global query scope? :)

Kryptonit3's avatar

ok, so I think this works :)

    /**
     * Apply scope on the query.
     *
     * @param \Illuminate\Database\Eloquent\Builder  $builder
     * @param \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder
            ->where('active', true)
            ->whereHas('user', function($q) {
                $q->whereNested(function($r) {
                    $r->where('account_type', 'company')
                      ->where('stripe_active', true)
                      ->orWhere('account_type', 'company')
                      ->whereNotNull('subscription_ends_at')
                      ->where('subscription_ends_at', '>', Carbon::now())
                      ->orWhere('account_type', 'company')
                      ->whereNotNull('trial_ends_at')
                      ->where('trial_ends_at', '>', Carbon::today());
                });
            });
    }
pmall's avatar

@Kryptonit3 It is very ugly to run this query every time you select an user. i strongly suggest you to use a custom collection like I mentioned above.

Kryptonit3's avatar

@pmall - Not sure how I would do this for the Job model. This needs to be filtered like that everytime the Job model is called. Be it from Job:: directly, or from $user->job-> or other related models like $state->jobs or $state->city->jobs. It needs to always be filtered with those rules. How else would I do it? I looked at the query generated and the time it takes and it didn't look like it was too intensive.

pmall's avatar

You dont even need a custom collection ;)

<?php namespace App;

use Illuminate\Database\Eloquent\Collection;

class User extends Eloquent
{
    public function newCollection(Array $users = [])
    {
        return (new Collection($users))->filter(function ($user) {

            return $user->subscribed();

        });
    }
}

Update : f*** I just realised you want it for the jobs.

fetch404's avatar
<?php namespace App;

use Illuminate\Database\Eloquent\Collection;

class Job extends Eloquent
{
    public function newCollection(array $items = [])
    {
        return (collect($items))->filter(function (Job $job) {
            return $job->user->subscribed() == true;
        });
    }
}

That should work...

1 like
Kryptonit3's avatar

@fetch404 but then you loose the ease of pagination with the database query. Also, there are a lot more relationships, just posted about $user->hasMany('Job') for readability but the $job also belongs to State and City and they both hasMany Job and there is a lot of count() going on across the site. The query method takes care of all this and doesn't require any additional code. It is done on the base model (Job) no matter who is asking for the relationship data (be it User, State, City etc).

I don't know if that would work with like $state->jobs->count() etc.

Thanks for posting that though, I am sure it will be helpful in certain situations for some users.

fetch404's avatar

The pagination can be done as well:

use Illuminate\Pagination\LengthAwarePaginator;
use Input;

$collection = // whatever it is
$page = Input::get('page') ?: 1;
$perPage = // jobs per page;
$pagination = new LengthAwarePaginator(
  $collection->forPage($page, $perPage), 
  $collection->count(), 
  $perPage, 
  $page
);

if ($pagination->lastPage() > $page) 
{
    $pagination = new LengthAwarePaginator(
        $collection->forPage(1, $perPage), 
        $collection->count(), 
        $perPage, 
        1
    );
}

Please or to participate in this conversation.