Since Laravel 5.3, the route implicit binding works as middleware called SubstituteBindings
. I used to work with Laravel 5.2 and upgraded to 5.3.
I have some custom middlewares in my application and in my tests I need to disable them. So, until now I used $this->withoutMiddleware()
in the test methods. But since the update to Laravel 5.3, withoutMiddleware
stops the route implicit binding, and all my tests fails.
I don't know if this should be considered as bug, but it is a huge problem for me.
Is there any way to set the SubstituteBindings
middleware as mandatory middleware? How can I still use implicit binding and test my tests without other middlewares?
Building on my comment above I had a look at registering a custom router which always adds SubstituteBindings
to the list of middleware if middleware was disabled. You can achieve it by registering a custom RoutingServiceProvider
and registering your own Router
class. Unfortunately since the route is created fairly early on in the app bootstrap process you also need to create a custom App class and use that in bootstrap/app.php
too.
RoutingServiceProvider
<?php namespace App\Extensions\Providers;
use Illuminate\Routing\RoutingServiceProvider as IlluminateRoutingServiceProvider;
use App\Extensions\ExtendedRouter;
class RoutingServiceProvider extends IlluminateRoutingServiceProvider
{
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function ($app) {
return new ExtendedRouter($app['events'], $app);
});
}
}
Custom router
This adds the middleware, it just extends the default router but overrides the runRouteWithinStack
method and, instead of returning an empty array if $this->container->make('middleware.disable')
is true, it returns an array containing the SubstituteBindings
class.
<?php namespace App\Extensions;
use Illuminate\Routing\Router;
use Illuminate\Routing\Route;
use Illuminate\Routing\Pipeline;
use Illuminate\Http\Request;
class ExtendedRouter extends Router {
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
// Make sure SubstituteBindings is always used as middleware
$middleware = $shouldSkipMiddleware ? [
\Illuminate\Routing\Middleware\SubstituteBindings::class
] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run($request)
);
});
}
}
Custom App Class
<?php namespace App;
use App\Extensions\Providers\RoutingServiceProvider;
class MyCustomApp extends Application
{
protected function registerBaseServiceProviders()
{
parent::registerBaseServiceProviders();
$this->register(new RoutingServiceProvider($this));
}
Using the custom app class
In bootstrap/app.php
change the line where the app is instantiated to:
$app = new App\MyCustomApp(
realpath(__DIR__.'/../')
);
--
Warning! I haven't fully tested this, my app loads and my tests pass but there could be issues that I haven't discovered. It's also quite brittle since if the Laravel base Router
class changes you might find things break randomly on future upgrades.
--
You might also want to refactor this so the list of middleware in the custom router always contains the SubstituteBindings
class so there isn't so much of a difference in behaviour if middleware is disabled.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With