Skip to content

Instantly share code, notes, and snippets.

@sebastiaanluca
Last active October 18, 2022 07:42
Show Gist options
  • Save sebastiaanluca/d61e9e2bc874615f82cfd679dee8edce to your computer and use it in GitHub Desktop.
Save sebastiaanluca/d61e9e2bc874615f82cfd679dee8edce to your computer and use it in GitHub Desktop.
Apply late route model binding. See https://github.com/laravel/framework/issues/6118#issuecomment-149951364 for more info.
<?php
declare(strict_types=1);
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
// Add the trait
use LateRouteBinding;
}
<?php
declare(strict_types=1);
namespace App\Http;
trait LateRouteBinding
{
/**
* @var array
*/
public static $lateBindings = [];
// Didn't know where else to put this, since we have no (variable) control over the internal Router class.
}
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Http\Kernel;
use Illuminate\Routing\ImplicitRouteBinding;
use Illuminate\Routing\RouteBinding;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class RouteMacroServiceProvider extends ServiceProvider
{
/**
* Register the application's response macros.
*
* @return void
*/
public function boot()
{
Route::macro('bindLateModel', function ($key, $class, Closure $callback = null) {
$this->bindLate($key, RouteBinding::forModel($this->container, $class, $callback));
});
Route::macro('bindLate', function ($key, $binder) {
Kernel::$lateBindings[$key] = RouteBinding::forCallback(
$this->container, $binder
);
});
Route::macro('substituteLateBindings', function ($route) {
foreach ($route->parameters() as $key => $value) {
if (isset(Kernel::$lateBindings[$key])) {
$route->setParameter($key, $this->performLateBinding($key, $value, $route));
}
}
return $route;
});
Route::macro('substituteLateImplicitBindings', function ($route) {
ImplicitRouteBinding::resolveForRoute($this->container, $route);
});
Route::macro('performLateBinding', function ($key, $value, $route) {
return call_user_func(Kernel::$lateBindings[$key], $value, $route);
});
}
}
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Routing\Registrar;
class SubstituteLateBindings
{
/**
* The router instance.
*
* @var \Illuminate\Contracts\Routing\Registrar
*/
protected $router;
/**
* Create a new bindings substitutor.
*
* @param \Illuminate\Contracts\Routing\Registrar $router
*
* @return void
*/
public function __construct(Registrar $router)
{
$this->router = $router;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
$this->router->substituteLateBindings($route = $request->route());
$this->router->substituteLateImplicitBindings($route);
return $next($request);
}
}
<?php
Route::group(['middleware' => ['auth', MiddlewareThatAppliesGlobalScopesFYI::class, SubstituteLateBindings::class]], function () {
// Your routes
});
@sebastiaanluca
Copy link
Author

Short howto:

  1. Copy LateRouteBinding, RouteMacroServiceProvider, and SubstituteLateBindings to your app
  2. Add the LateRouteBinding trait to your application's HTTP kernel
  3. Register RouteMacroServiceProvider in your config/app.php file
  4. Apply the SubstituteLateBindings middleware to your routes
  5. Use the $this->bindLate() method to explicitly bind your models

Optionally you could remove the default SubstituteBindings middleware from your Kernel so all bindings, even implicit ones, are done after.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment