skip to Main Content

I have a Laravel model Product and Vouchers defined as follows:

class Product extends Model {
    public function vouchers(): HasMany {
          return $this->hasMany(Voucher::class);
    }
}

and

class Voucher extends Model {
    public function product(): BelongsTo {
        return $this->belongsTo(Product::class);
    }
}

The voucher table has a column status. Values in status can be ‘active’ or ‘inactive’. I want to create a local scope so I can get the first active voucher on a product.

In the Product model, I defined the following local scope:

public function scopeActiveVouchers($query)
{
    return $query->vouchers->first()->where('status', 'active');
}

But this does not work. I always get

Property [vouchers] does not exist on the Eloquent builder instance.

How can I change the local scope so that the first active voucher for a given product is returned?

2

Answers


  1. Query Scope is supposed to add additional query parameter to the model itself, not to a related model.

    You shouldn’t add a scope in your Product that supposed to add additional parameter on your Voucher model. Otherwise, you have to manually join the voucher table to load it when your Product scope is called.

    You can instead just add additional relation in your product model for querying specific Voucher

    class Product extends Model {
        
        public function vouchers(): HasMany {
            return $this->hasMany(Voucher::class);
        }
    
        public function active_voucher() {
            return $this->hasOne(Voucher::class)->where('status', 'active');
        }
        
    }
    

    then you can do something like;

    Product::with('active_voucher')->paginate();
    

    or even just do it on your query directly

    Product::with(['vouchers' => function( $query) {
        $query->where('status', 'active')->take(1);
    }])->paginate();
    

    If you still want to use scope, you could try

    public function scopeActiveVouchers(IlluminateDatabaseEloquentBuilder $query) {
            
        return $query->with(['vouchers' => function($query) {
            $query->where('status', 'active')->take(1);
        }]);
    }
    

    then

    Product::activeVouchers()->paginate();
    
    Login or Signup to reply.
  2. I would do something like this. first define a scope.

    public function scopeActiveVouchers($query)
       {
         return $query->whereHas('vouchers', function($query) {
           $query->where('status', 'active');
          });
       }
    

    Then use it like this:

    Product::activeVouchers()->first()
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search