Skip to content

Instantly share code, notes, and snippets.

@LachlanArthur
Created September 14, 2024 09:43
Show Gist options
  • Save LachlanArthur/7946eaa9daa26f73bf416abbe4852734 to your computer and use it in GitHub Desktop.
Save LachlanArthur/7946eaa9daa26f73bf416abbe4852734 to your computer and use it in GitHub Desktop.
Profiling middleware in Laravel 11 with the Laravel DebugBar
<?php
namespace App\Providers;
use App\Http\Middleware\Profiled;
use Closure;
use DebugBar\DebugBar;
use Illuminate\Contracts\Http\Kernel as HttpKernelContract;
use Illuminate\Http\Request;
use Illuminate\Routing\Events\RouteMatched;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
class MiddlewareProfiling extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
if (class_exists(DebugBar::class) && !$this->app->runningInConsole()) {
$this->app->booted($this->enableProfiling(...));
}
}
protected function enableProfiling()
{
/**
* @var \Illuminate\Foundation\Http\Kernel $kernel
*/
$kernel = $this->app->get(HttpKernelContract::class);
$kernel->setGlobalMiddleware(array_map($this->wrapMiddleware(...), $kernel->getGlobalMiddleware()));
\Event::listen(function (RouteMatched $routeMatched) {
$router = $this->app->make(Router::class);
$routeMatched->route->computedMiddleware = array_map($this->wrapMiddleware(...), $router->gatherRouteMiddleware($routeMatched->route));
$routeMatched->route->computedMiddleware[] = fn(Request $request, Closure $next) => measure('Route response', fn() => $next($request));
});
}
protected function wrapMiddleware($middleware)
{
if (is_string($middleware)) {
return Profiled::class . ':' . base64_encode($middleware);
}
if ($middleware instanceof Closure) {
return Profiled::wrapClosure($middleware);
}
\Log::warning('Failed to profile middleware', ['middleware' => $middleware]);
return $middleware;
}
}
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class Profiled
{
public static function wrapClosure(Closure $middleware)
{
return fn(Request $request, Closure $next) => (new static)->handle($request, $next, $middleware);
}
/**
* Handle an incoming request.
*
* @param Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param class-string|Closure $middleware
* @return Response
*/
public function handle(Request $request, Closure $next, $middleware): Response
{
$name = $this->middlewareName($middleware);
start_measure("{$name} [Entry]");
$response = $this->proxy($middleware, $request, function (Request $request) use ($name, $next) {
stop_measure("{$name} [Entry]");
$response = $next($request);
start_measure("{$name} [Exit]");
return $response;
});
stop_measure("{$name} [Exit]");
return $response;
}
protected function middlewareName($middleware): string
{
if (is_string($middleware)) {
return base64_decode($middleware);
}
if ($middleware instanceof Closure) {
$reflect = new \Laravel\SerializableClosure\Support\ReflectionClosure($middleware);
return $reflect->getShortName();
}
throw new \InvalidArgumentException('Invalid middleware');
}
protected function proxy($middleware, Request $request, Closure $next): Response
{
if (is_string($middleware)) {
[$name, $parameters] = $this->parseMiddlewareString(base64_decode($middleware));
return app()->make($name)->handle($request, $next, ...$parameters);
}
if ($middleware instanceof Closure) {
return $middleware($request, $next);
}
throw new \InvalidArgumentException('Invalid middleware');
}
/**
* Parse full middleware string to get name and parameters.
*
* @param string $middleware
* @return array
*
* @see \Illuminate\Pipeline\Pipeline::parsePipeString
*/
protected function parseMiddlewareString(string $middleware)
{
[$name, $parameters] = array_pad(explode(':', $middleware, 2), 2, []);
if (is_string($parameters)) {
$parameters = explode(',', $parameters);
}
return [$name, $parameters];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment