skip to content

In Laravel, what does php artisan make:middleware generate, and how does the generated handle() method let a request through or stop it?

level: juniorimportance: must knowfreq 74%

answer

  1. lands in app/Http/Middleware
  2. handle(Request $request, Closure $next): Response
  3. return $next($request) to continue
  4. return a redirect or response to stop
  5. --test, --pest or --phpunit adds a test

basics

~10 s

make:middleware creates a class in app/Http/Middleware with handle(Request $request, Closure $next): Response. Returning $next($request) passes the request inward; returning your own response, such as a redirect, stops it before the controller runs.

solid answer

~40 s

`php artisan make:middleware EnsureRestaurantIsOpen` writes `app/Http/Middleware/EnsureRestaurantIsOpen.php` from the framework's middleware stub: a plain class with one method, `handle(Request $request, Closure $next): Response`, whose body is `return $next($request);`. `$next` is the rest of the pipeline; calling it with the request lets inner middleware and the controller run and gives you back their response, which you must return. To stop the request, you return a response of your own instead, for example `redirect()->route('menu')` or `response()->json([...], 503)`, and the controller never runs. The return type is Symfony's `Response`, which every Laravel response extends. The command also accepts `--test`, `--pest` or `--phpunit` to generate a matching test, and the class still has to be registered before any request reaches it.

code

bash · 1 line
bash
php artisan make:middleware EnsureRestaurantIsOpen --pest

go deeper

for a junior

Recall the generated signature, handle(Request $request, Closure $next): Response, and the two outcomes: return $next($request) or return your own response.

for a middle

Explain what $next represents, why the return type matters, and how redirect, JSON and abort() differ as ways to stop a request.

for a senior

Keep middleware small and single-purpose, test the reject and pass paths, and check that each early response suits both browser and API clients.

for a principal

Decide which rules belong in middleware versus policies or form requests, so request filtering stays predictable across teams.

## What make:middleware produces A **middleware** in Laravel is a class that sits between the incoming HTTP request and the code that handles it. The generator creates one for you: ```bash php artisan make:middleware EnsureRestaurantIsOpen ``` The command, `Illuminate\Routing\Console\MiddlewareMakeCommand`, writes the file into the `App\Http\Middleware` namespace, which is `app/Http/Middleware/` in a default application. The body comes from the framework's `middleware.stub`: ```php <?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class EnsureRestaurantIsOpen { public function handle(Request $request, Closure $next): Response { return $next($request); } } ``` A few facts about the generator itself: - It takes a single `name` argument; nested names such as `Orders/EnsureCartIsNotEmpty` create a sub-namespace. - It accepts `--test`, `--pest` and `--phpunit` to create a matching test class alongside. - After `php artisan stub:publish`, a customised `stubs/middleware.stub` in the project root is used instead of the framework's copy. - The class is **not registered** anywhere. Until it is attached to a route, a group or the global stack, it never runs. ## The two arguments of handle() - `$request` is the current `Illuminate\Http\Request`: headers, input, the authenticated user, the matched route (for route middleware). - `$next` is a `Closure` that represents **everything inside this middleware**: the remaining middleware and, finally, the controller. Calling `$next($request)` runs all of it and returns the resulting response. The return type `Symfony\Component\HttpFoundation\Response` covers every response Laravel builds: `Illuminate\Http\Response`, `JsonResponse`, `RedirectResponse`, streamed and file responses all extend it. ## Letting the request through The stub's single line is the pass-through case. When a middleware has nothing to object to, it returns whatever `$next($request)` returns. Forgetting the `return` is a classic bug: `handle()` then returns `null`, and with the `: Response` return type PHP throws a `TypeError` instead of sending the page. ## Stopping the request To stop a request, the middleware simply **does not call `$next`** and returns a response of its own: ```php public function handle(Request $request, Closure $next): Response { if (! $this->kitchen->isOpen()) { return redirect()->route('menu')->with('status', 'The kitchen is closed.'); } return $next($request); } ``` Common ways to build that response: | Response | Typical use in a food-ordering app | |---|---| | `redirect()->route('menu')` | Browser users sent back to the menu page | | `response()->json(['message' => 'Kitchen closed'], 503)` | Mobile or SPA clients calling the API | | `response()->view('closed', status: 503)` | A friendly HTML page | | `abort(403)` | Throws instead of returning; the exception handler renders the response | Whatever is returned travels back out through the middleware that wrapped this one, exactly like a controller's response would. ## What the generated class does not decide The class file only defines behaviour. Three decisions live elsewhere: 1. **Where it runs**: registration in `bootstrap/app.php` or on routes. 2. **Which parameters it receives**: extra arguments after `$next` come from the route string, for example `role:admin`. 3. **Which services it uses**: dependencies are type-hinted in the constructor, because the container builds the middleware. ## Reading the generated docblock The stub annotates `$next` as `Closure(Request): (Response)`. PHP itself cannot express a closure's signature in a type declaration, so this docblock is for static analysers and IDEs: it tells them that `$next` takes the request and returns a response, which lets them flag a `return $next;` (returning the closure itself) or a missing return. Keep it when editing the class. ## Common mistakes - Changing `$request` after calling `$next`, expecting the controller to see the change; by then the controller has already run. - Returning a string or an array from `handle()`; unlike a controller action, the declared `: Response` type rejects it. - Doing the rejection check **after** `$next`, when the controller has already performed the order it was supposed to block. ## Checklist for a new middleware - Name it after the rule it enforces (`EnsureRestaurantIsOpen`), not the mechanism. - Keep the `: Response` return type and always return something. - Return early for the rejection path; keep the happy path as `return $next($request);`. - Write the test with `--test` or `--pest` while the behaviour is fresh.

  • What happens if a Laravel middleware calls $next($request) but forgets to return its result?
    `handle()` returns `null`. Because the generated method declares `: Response`, PHP throws a `TypeError` at that point, the exception handler renders a 500, and the controller's response is lost even though the controller ran. Always `return $next($request);`.
  • Does a class created by make:middleware run as soon as it exists in app/Http/Middleware?
    No. The generator only writes the file. Laravel does not scan that directory; the class runs only after it is attached to a route or group, or added to the global stack in `bootstrap/app.php`.

saying these in an interview costs you the question

  • Expects the new middleware to run automatically once the file exists
  • Calls $next($request) and forgets to return the response
  • Thinks returning false from handle() rejects the request
  • Believes the controller still runs after an early redirect
  • Says make:middleware also edits app/Http/Kernel.php