Landlord: Scope Not being applied in middleware

I added a ScopeRequestByUser middleware like so

public function handle($request, Closure $next)
{

    if ($request->ajax() || $request->wantsJson()) {
        Landlord::addTenant('user_id', Auth::guard('api')->id());
    }

    return $next($request);
}

Now this works perfectly when I add this to my global middleware stack like this.

protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \App\Http\Middleware\ScopeRequestByUser::class,
];

However, I need it to be applied only on the api middleware group. When I add to the group in the middleware file it does not work.

    protected $middlewareGroups = [
        'web' => [
          ...
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
            \App\Http\Middleware\ScopeRequestByUser::class,
        ],
    ];

I even tried adding it ot the route middleware and then calling it both in the routes file and in the api middleware group. Both of them are not working.

    protected $routeMiddleware = [
        ...
        'scope' => \App\Http\Middleware\ScopeRequestByUser::class,
    ];

Not sure, what is going wrong here.

About this issue

  • Original URL
  • State: open
  • Created 8 years ago
  • Reactions: 4
  • Comments: 22 (10 by maintainers)

Most upvoted comments

It’s not only using model observers that cause the model’s boot sequence to happen before tenants have been added - I was using dependency injection to instantiate a model repository, and that also caused the model to boot before the route middleware could run and add a tenant. If I added the middleware to the global list (as well as the session and cookie middlewares so I could access the logged in user) then the tenant would be set first. However, I don’t want it set on all routes.

The solution was using the suggestion in #61. My middleware uses clearBootedModels just before setting the tenant id:

public function handle($request, Closure $next)
{
    $user = app('Dingo\Api\Auth\Auth')->user();

    \Illuminate\Database\Eloquent\Model::clearBootedModels();

    foreach($user->tenantIds() as $name => $id)
    {
        Landlord::addTenant($name, $id);
    }

    return $next($request);
}

My temporary solution (not the best) was to extend trait, and then I added the tenant after getting the TenantManager from laravel container.


namespace App\Traits;

use HipsterJazzbo\Landlord\BelongsToTenants as BaseBelongsToTenants;
use HipsterJazzbo\Landlord\TenantManager;
use Illuminate\Database\Eloquent\Model;

trait BelongsToTenants
{
    use BaseBelongsToTenants;

    public static function bootBelongsToTenants()
    {
        // Grab our singleton from the container
        static::$landlord = app(TenantManager::class);

        //My temporarily approach
        static::$landlord->addTenant('workspace_id', currentWorkspace()->id);

        // Add a global scope for each tenant this model should be scoped by.
        static::$landlord->applyTenantScopes(new static());

        // Add tenantColumns automatically when creating models
        static::creating(function (Model $model) {
            static::$landlord->newModel($model);
        });
    }

}

We changed our approach a bit. The issue is with booting models before the middleware has run. Registering model observers will boot the model, since Model::observe will do new static;, which calls the constructor and boots the model.

So now I am registering observers like this:

Event::listen('eloquent.booted: ' . User::class, function () {
    User::observe(UserObserver::class);
});

This ensures that the observer isn’t added until after the User model has been booted by some other process.

Also added an issue for this, can hopefully spark a discussion: https://github.com/laravel/framework/issues/16715

I haven’t dug completely down through the call stack to see in what order things are happening in here, but the Kernel seems to be handling running the request through the global $middleware and then the router is responsible for running the request through the $routeMiddleware and $middlewareGroups.

The whole point here is to have the middleware run (and tenant ID set) BEFORE we attempt to set the global scope in models (meaning before any model is booted).

In my case, that meant having to move the middleware to the global $middleware. Having it in the middleware handled by the router fails.

I am having a similar issue. My problem seems to be the timing of when the tenant is being registered, because some models are being booted before the request is run through the middleware where the tenant is set.

From what I can tell, this is the order of things:

  1. Kernel runs sendRequestThroughRouter
  2. In this method, bootstrap() is run
  3. Bootstrapping ends with registering providers, then booting them. In some cases, like if you register a model observer, the models will be booted now, which will attempt to set the global scope for tenant. At this point though, the middleware has not been run yet, so no tenant is registered, hence setting up the global scope here fails…
  4. After this, the request is run through the middleware where you are setting your tenant. Any models booted after this point will work as expected. Models that were booted prematurely in the previous step will not have the global scope set since they can only be booted once.