pieterdejong's avatar

Dynamic scopes

I'm quite new to using scopes, but i ran into a problem which i do nu fully understand.

For a project, i'm using a company_id on basically all of my models. Each time i would fetch some models is could write ->where(company_id, '=', auth()->user()->userable->id);

This will work but there is also a big change i might forget to write the where statement, and this would mean exposure of data. So using a global scope looks like a good solution.

But when using a scope on my employee model i get a error saying

Undefined property: App\Models\User::$userable

It looks like the scope is messing things up, only when used on the employee model. Maybe because userable is an instance of employee and there is also a scope on employee using userable auth()->user()->userable.

But like i said i actually don't understand. So if someone could explain why this is an issue, i can appreciate that.

Below is my model structure

<?php

namespace App\Scopes;


use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;

class CompanyScope implements Scope
{

    public function apply(Builder $builder, Model $model)
    {
        $employee = Auth::user()->userable;
        $builder->where('company_id', '=', $employee->company_id);
    }
}
<?php

namespace App\Models;

use App\Scopes\CompanyScope;
use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    protected $guarded = ['created_at', 'updated_at', 'deleted_at', 'id'];

    protected $table = 'employees';

    protected static function booted(): void
    {
        static::addGlobalScope(new CompanyScope);
    }


    public function company()
    {
        return $this->belongsTo(Company::class);
    }

    public function user()
    {
        return $this->morphOne(User::class, 'userable');
    }

}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;
    use HasRoles;

    protected $appends = [
        'profile_photo_url',
    ];

    public function userable()
    {
        return $this->morphTo();
    }

}
0 likes
4 replies
bobbybouwmann's avatar

So the thing here is that the model probably doesn't understand what's going on because it's pointing to itself or some other model that is related.

Maybe this helps you to make it work

class CompanyScope implements Scope
{
		public function apply(Builder $builder, Model $model)
		{
				$employee = $model instanceof \App\Models\Employee
						? $model
						: Auth::user()->userable;
				
				$builder->where('company_id', '=', $employee->company_id);
		}
}

It's also wise to add an extra check for if the user is logged in and so on. Otherwise, you get errors as well in the long run ;)

pieterdejong's avatar

Thanks for the response, unfortunately $model is an empty Employee class in this particular case. So i get a response Trying to get property 'company_id' of non-object

bobbybouwmann's avatar

You can check for that as well right? If the Employee class is not an existing model it's impossible to retrieve the company_id

$employee = $model instanceof \App\Models\Employee
		? $model
		: Auth::user()->userable;
				
// Apply the scope only if the employee exists in the database
if ($employee->exists()) {
		$builder->where('company_id', '=', $employee->company_id);
}
pieterdejong's avatar

Hmm that was not what i meant. I looks like $model is always an empty class. The employee does exist.

Please or to participate in this conversation.