Swaz's avatar
Level 20

Shortening Eloquent Call

I'm playing around with a learning project. I have companies, and users. Each company has many users. Every other table in my project is tied to a specific company (ex. company->projects, company->posts, etc.)

I find myself making the same Auth::user()->company call in every single controller.

Example: Auth::user()->company->projects; or Auth::user()->company->posts;

Is there a way to globally set it, so that when I type Project::all(); it's always only projects for the logged in users company... or is there a better way to go about what I am trying to do?

0 likes
12 replies
Swaz's avatar
Level 20

@JarekTkaczyk Thank you for the example, however I was not able to get it to work. I keep getting an error Declaration of App\Models\Scopes\UsersCompanyScope::apply() must be compatible with Illuminate\Database\Eloquent\ScopeInterface::apply(Illuminate\Database\Eloquent\Builder $builder, Illuminate\Database\Eloquent\Model $model)

This might be a bit over my head for now.

Swaz's avatar
Level 20

@JarekTkaczyk

<?php namespace App\Models\Scopes;

use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;

class UsersCompanyScope implements ScopeInterface {

    public function apply(Builder $builder)
    {   
        $column = $builder->getModel()->getCompanyIdColumn();

        $builder->where($column, 1); //company_id hardcoded for testing

        $this->extend($builder);
    }

    public function remove(Builder $builder)
    {   
        $query = $builder->getQuery();

        //$column = $builder->getModel()->getCompanyIdColumn();

        foreach((array)$query->wheres as $key => $where) {

            if($where['column'] == 'company_id') {

                unset($query->wheres[$key]);

                $query->wheres = array_values($query->wheres);
            }
        }
    }
}
JarekTkaczyk's avatar

@Swaz Oh ok, in fact the scope changed a bit in L5, my bad. Simply change the method signatures to:

    public function apply(Builder $builder, Model $model);
    public function remove(Builder $builder, Model $model);

nothing else needs to be changed.

Of course Model is Illuminate\Database\Eloquent\Model so also add use statement to the class.

Swaz's avatar
Level 20

@JarekTkaczyk That fixed the error I was getting. I am not sure the syntax to use in the apply() method. This was my best attempt, but now it gives me Error code: ERR_EMPTY_RESPONSE. I think for the time being, I will just keep using Auth::user()->company in my controllers, until I get a better understanding of all this. Thank you for you help.

public function apply(Builder $builder, Model $model)
{   
    $column = $model->getQualifiedCompanyIdColumn();

    $builder->where($column, 1); //company_id hardcoded for testing

    $this->extend($builder);
}
JarekTkaczyk's avatar

@Swaz Don't give up mate :) Create a gist with all the files involved - scope, model and controller that handles the request and I'm sure we'll find the error.

JarekTkaczyk's avatar

@pmall It's a tenant-like call I suppose - You want only the records that are related to your company, not you directly.

pmall's avatar
pmall
Best Answer
Level 56

Yes it messed so much with my head I deleted my post lol.

I don't think a scope is a good solution here why not just :

abstract class Controller extends BaseController {

    public $category;

    public function __construct()
    {
        $this->category = Auth::user()->category;
    }

}

It is hard to shorten $this->category. And I think it is clearer than scoping every models.

Or

public function apply(Builder $builder, Model $model)
{   
    $builder->where('company_id', Auth::user()->company->id);
}

Or create a scope fromCompany for every models (to manage both hasMany and belongsToMany kind of relationship with company) and call it in the scope.

But Im sure scoping all your models like this will bite your neck somehow.

JarekTkaczyk's avatar

@pmall I think it's up to the OP - depending on his needs. Anyway in multi-tenant apps (row based) global scope is definitely the way to go.

You may use it in models and even on Query\Builder - you can read more http://softonsofa.com/laravel-query-builder-global-scope-how-to-use-custom-connection-and-query-builder-in-laravel-4/

If I'm not mistaken, Laravel5 introduced easier way of extending connection/builder, but haven't played with it yet.

Swaz's avatar
Level 20

Thanks everyone. I don't really have any particular needs, I am just playing around trying to learn Laravel. I think for this case, it makes the most sense, if anything, to do what @pmall suggested and put $this->category = Auth::user()->category; in the constructor.

Please or to participate in this conversation.