2

In my app I've got a group of routes which need some bootstraping before dispatching.

To illustrate the situation:

There is a special routes group with prefix 'app'. All of this routes have also some params:

site.dev/app/index?age=11&else=af3fs4ta21

Without these params user shouldn't be allowed to access route. I've got it done by creating a simple route middleware.

if (!$request->exists('age') || !$request->exists('else')) {
        return redirect('/');
}

Next step is to initialize a class which takes route parameters as a construct arguments. Then param "else" is being used as a argument to db calls. I need to access this class in every route from /app route group.

In order to achive that I tried setting up a serviceprovider:

public function register()
{
    $this->app->singleton(Dual::class, function ($app) {
        return new Dual($this->app->request->all());
    });
}

Then I created a special controller extending BaseController and passing Dual class to its constructor.

class DualController extends Controller
{
    public function __construct(Request $request, Dual $dual)
    {
        $this->middleware(\App\Http\Middleware\DualMiddleware::class);

        $this->dual = $dual;
    }
}

And then every single controller is extending DualController and accessing Dual class by $this->dual->method().

It is working if route params are in their place and there is already a row in a database.

The problem

This middleware is executed AFTER ServiceProvider & DualController are initializing class Dual. So, middleware is not really working. If route params are not present it is going to fail.

Moreover, in case that there is no required row in database for some reason, Dual class will not be initialized (as it depends on calls to db) and whole app will crash saying that I am trying to perform operations on null.

Desired behaviour

First check route for params presence.

Second, check if there is row in db with key from route.

Third - try to initialize Dual class and pass it to all controllers used by route group /app.

If any of the steps fail -> display proper message.

Part of dual class:

class Dual
{
    protected $client = null;
    public $config = [];

    public function __construct($config)
    {
        $this->config = $config;
        $this->bootstrap();
    }

    public function getEli()
    {
        $eli = Eli::where(['else' => $this->config['else']])->first();
        return $eli;
    }

    public function instantiateClient()
    {
        $client = Client::factory(Client::ADAPTER_OAUTH, [
            'entrypoint' => $this->getEli()->eli_url,
            'client_id' => '111',
            'client_secret' => '111',
        ]);

        $client->setAccessToken($this->getEli()->accessToken()->first()->access_token);

        return $client;
    }

    public function getClient()
    {
        if ($this->client === null)
        {
            throw new \Exception('Client is NOT instantiated');
        }

        return $this->client;
    }

    public function bootstrap()
    {
        $this->client = $this->instantiateClient();
    }
ficus
  • 196
  • 2
  • 17
  • Why can't you check if a user is allowed in the middleware? – Alexey Mezenin Jan 13 '18 at 12:28
  • @AlexeyMezenin If by user you mean registered && logged user - there is no such thing in this app, and won't be. I tried to check params and if such model exists in DB in a middleware, but it won't work as long as laravel execute route middleware after constructing and initializing class Dual in controller. – ficus Jan 13 '18 at 12:41
  • Try making it a route middleware add a key in ``app/Http/Kernel.php`` then define it in your routes file ``->middleware('something')`` – ahmad Jan 13 '18 at 12:41
  • @ahmad thanks for response. I tried that at first, still - middleware is executed after serviceprovider. On the other hand, global middleware was working fine (first middleware then construct), however global is global, blocked all other routes in app. – ficus Jan 13 '18 at 12:43
  • @AlexeyMezenin I've updated question with part of dual class which cause problems. Problem is mostly with part getEli()->accessToken() as it try to perform it on null. – ficus Jan 13 '18 at 12:49
  • Why do you insist on injecting it to your base controller? remove the binding from service provider & just modify it to ``$this->dual = new Dual($request->all())`` then your middleware should be fine – ahmad Jan 13 '18 at 12:56
  • @ahmad unfortunately not. I tried it once and I tried it again to make sure. I removed the bindings and did what you said. Still, whatever is in controller __construct method is executed before route middleware. I even tried to simply dd('a') in middleware and dd('b') in controller construct - got 'b' as a result – ficus Jan 13 '18 at 13:21

1 Answers1

1

You can do this in middleware:

$isElseExists = Model::where('else', request('else'))->first();
if (request('age') && request('else') && $isElseExists) {
    return $next($request);
} else {
    return back()->with('error', 'You are not allowed');
}

If everything is fine, controller method will be executed. Then you'll be able to inject Dual class without any additional logic.

If something is wrong, a user will be redirected to previous URI with error message flashed into session.

Alexey Mezenin
  • 158,981
  • 26
  • 290
  • 279
  • Yeah, the thing is that for some reason Controller Method is executed before this middleware. – ficus Jan 13 '18 at 12:52
  • If you'll check everything is the middleware and user will not be allowed to access the route, the logic in the controller method will not be executed. – Alexey Mezenin Jan 13 '18 at 13:07
  • Even if I do dd() or exit() or whatever in the middleware, logic from controller __construct are executed first. I suppose it is working in the way: -> create controller class -> check middleware -> check specified method in controller? – ficus Jan 13 '18 at 13:19
  • @ficus you've asked about a method. You can move the logic from the constructor and resolve the class in a controller method instead. Also, why is it bad if `Dual` class will be injected? – Alexey Mezenin Jan 13 '18 at 13:25
  • Indeed I can, but in this case I need to reinitialize it in every single method and in every controller from this group, don't I? It is not too convenient and also I've doubts about performance in this case. I used service container at first to prevent it from reinitializing every time. – ficus Jan 13 '18 at 13:31
  • @ficus it will not be reinitialized each time. With each request, it will be initialized just once. You don't even need to use create a singleton here. And performance is absolutely the same, it doesn't matter where you resolve the class. Also, you didn't answer why is it bad if the class will be injected even if a user will not be allowed to see the page? – Alexey Mezenin Jan 13 '18 at 13:37
  • But still - how to make this variable global in all methods/controllers without need to write $this->sm = new smt($argS); at the beggining of each method? Because this class is binded to route parameters. If for some reason user is not allowed - whether it's because params in route are not present or params from route doesnt match with db records, app will simply crash as class initialization fail and throw errors. Maybe I am still missing your point tho – ficus Jan 13 '18 at 13:43
  • 1
    @ficus you can bind the class and do this globally `app('dual')->instantiateClient()` in all controllers, views, models etc. – Alexey Mezenin Jan 13 '18 at 13:51
  • @ficus also, you're passing `$request->all()` as parameter, but you can also do this in the Dual constructor `$config = $request->all()` or even `$config = request()->all()` without injecting `Request` object. – Alexey Mezenin Jan 13 '18 at 13:53
  • Now I am a bit confused. By binding the class (app('dual')), you mean - bind it to service container right (appserviceprovider)? If so - we are back to the beggining. And - what is important - only specified group of routes will make use of this class, i.e. homepage will crash, as service container will try to pass class istance? Valid point with request object, thanks :-) – ficus Jan 13 '18 at 14:00
  • @ficus I don't understand why it could crash. Nothing bad will happen, if a user is not allowed, middleware will redirect him on the previous page with error message. – Alexey Mezenin Jan 13 '18 at 14:06
  • if middleware will work before injecting class, then yes, everything is fine. Otherwise class fails and error is throwed. Nevermind. Anyway, middleware work only for controlled methods (other than _construct). Its okay. Now, the only problem is to inject this class to all this controllers without need to repeat 'new Class()' in every single method. You mentioned binding it somewhere - did you mean appservicecontainer? Can you provide some instuction or docs, how to bind it somewhere (and initialize?) so I can use just app('dual')->method() whereever I want in controllers? – ficus Jan 13 '18 at 14:18
  • Ok, I think i figured it out already. Gonna make some tests, I will be back then. Thanks a lot :-) – ficus Jan 13 '18 at 14:25