What does 'return $next($request)' do in Laravel middleware? - laravel

Please respect that I'm new to programming and Laravel, so this question might seem a little odd to the most of you.
But I think this is what stackoverflow is for, so:
When I created a new middleware with the command php artisan make:middleware setLocale there was already the handle-function with this code in it:
return $next($request);
and I'm wondering what exactly this line does.

$next($request) just passes the request to next handler.
Suppose you added a middleware for checking age limit.
public function handle($request, Closure $next)
{
if ($request->age <= 18) {
return redirect('home');
}
return $next($request);
}
when age is less than 18 it will redirect to home but when the request passes the condition what should be done with the request? it will pass it to next handler.Probably to the register user method or any view.

This is explained in the documentation:
To pass the request deeper into the application (allowing the middleware to "pass"), call the $next callback with the $request.
It's best to envision middleware as a series of "layers" HTTP requests must pass through before they hit your application. Each layer can examine the request and even reject it entirely.
https://laravel.com/docs/5.8/middleware#defining-middleware

Related

Laravel - Controller dependency is injected before middleware is executed

So I created a middleware to limit the data a connected user has access to by adding global scopes depending on some informations:
public function handle(Request $request, Closure $next)
{
if (auth()->user()?->organization_id) {
User::addGlobalScope(new OrganizationScope(auth()->user()->organization));
}
return $next($request);
}
The middleware is added to the 'auth.group' middleware group in Kernel.php which is used in web.php:
Route::middleware(['auth.group'])->group(function () {
Route::resource('users', UserController::class);
});
Then in the controller, I would expect a user to get a 404 when trying to see a page of a user he has no rights to. But the $user is retrieved before the middleware applies the global scope!
public function show(User $user, Request $request) {
// dd($user); // <= This actually contains the User model! It shouldn't, of course.
// dd(User::find($user->id)); // <= null, as it should!
}
So, the dependency is apparently calculated before the middleware is applied. If I'm trying to move the middleware into the 'web' group in Kernel.php it's the same. And in the main $middleware array, the authenticated user's data is not available yet.
I found this discussion that seems to be on topic : https://github.com/laravel/framework/issues/44177 but the possible solutions (and Taylor's PR) seems to point to a solution in the controller itself. Not what I'm trying to do, or I can't see how to adapt it.
Before that I was applying the global scopes at the Model level, in the booted function (as shown in the docs). But I had lots of issues with that - namely, accessing a relationship from there to check what is allowed or not is problematic, as the relationship call will look for something in the Model itself, and said model is not ready (that's the point of the booted method, right...). For example, checking a relationship of the connected user on the User model has to be done with a direct query to the db, that will be ran every time the Model is called... Not good.
Anyway, I like the middleware approach as it is a clean way to deal with rights as well, I think. Any recommandation?
Not a recommendation, just my opinion.
This issue is just because of that Laravel allow you add middleware in controller constructor, and that's why it calculate before midddleware in your case.
I agree that middleware is a clean way to deal with auth, but i also think that you are not completely doing auth in your middleware, for example if you create a new route will you need to add something auth action into your new controller or just add auth middleware to route?
If does needs add something to controller, that means your auth middleware is just put some permissions info into global scope and you are doing the auth in controller which i think it's not right.
Controller should be only control the view logic, and you should do full auth in your auth middleware, once the request passed into your controller function that means user passed your auth.
For some example, if you auth permissions like below, you can just add auth middleware to new route without any action in your controller when you trying to create new route.
public function handle(Request $request, Closure $next)
{
if (auth()->user()->canView($request->route())) { // you should do full auth, not just add informations.
return $next($request);
}
else
abort(404);
}

How can I access Laravel Response Object from within controller?

I have middleware where I am assigning http headers to the request/response.
$response = $next($request)->header('x-robots-tag', 'noindex', false);
In the middleware, I can also apply this line after executing the above, to get the value I had just set...
echo $response->headers->get('x-robots-tag');
But, I want to access this outside of middleware but I'm not sure how to get the Response object back to achieve this.
How can I get the $response object from within my controller?
$response = \WHAT\GOES\HERE?;
echo $response->headers->get('x-robots-tag');
I can't seem to figure out what to put in the \WHAT\GOES\HERE part to access the response object again.
Update #1:
Still unresolved, but part of the problem appears to be that in order to add the header tags to the Response object within middleware requires $next($request) and the $next Closure causes the response processing to be done after the controller code has executed. So even though I'm not sure how to target the Response object from within the controller, it doesn't look like it would have the header tag assigned until afterward anyway.
I could set the headers directly in PHP in the middleware with
public function handle($request, Closure $next /*, $tags */)
{
$tags = array_except(func_get_args(), [0,1]);
if( count($tags) > 0){
header('x-robots-tag: ' . implode(', ', $tags));
}
return $next($request);
}
and then access it in the controller by pulling it out of the headers_list() but that's not ideal and working outside of the laravel ways...
For context, the idea was to assign middleware to routes and with the middleware assign the x-robots-tag response header with the desired attributes. noindex, nofollow, whatever... Then I was hoping to capture this and populate the equivalent meta tags accordingly using the data provided to the x-robots-tag. A two-birds with one stone sort of approach, but it has proven more difficult than I had expected.

How does Laravel know whether a middleware should be run after the request handled?

I read the source code, and there is only a pipeline which reads all middlewares as an array. These middlewares should be run before the request dispatchToRouter.
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
However, if I created an after-middleware, the after-middleware should be run after the request handled. here and when the after-middleware being execute in the laravel source code?
According to laravel official documentation,
Whether a middleware runs before or after a request depends on the middleware itself.
So, basically it depends on the handle function of the middleware. Normally we execute middleware just before handling the request like this:
public function handle($request, Closure $next)
{
// Perform some operation for Ex. checking user role
return $next($request);
}
In above function, we execute some operation before sending request to perform operation.
In the another case, middleware would perform its task after the request is handled by the application like this:
public function handle($request, Closure $next)
{
$response = $next($request);
// Perform some operation after the request is handled by the application
return $response; /* finally, response is returned */
}
In summary, In before middleware, we perform some operations at first and then send request to the application to get response which is returned to client end. In after middlware, we send request to the application at first to get response then we perform our actions and finally we return the response from middleware to client end.
You can see official docs: https://laravel.com/docs/5.8/middleware#defining-middleware

Is Laravel introducing new programming syntax?

What is this Closure $next. Ok I am guessing an object but how a object reference accepts an argument $request? Shouldn’t it call method. Closure in php are anonymous function but is it that the next is an anonymous function variable defined else where? I see the handle method is in Class Frameguard but the above example didn’t even instantiate Frameguard Class. Am I correct to say Laravel does not follow oop syntax?
public function handle($request, Closure $next)
{
if ($request->age <= 200) {
return redirect('home');
}
return $next($request);
}
Now, it does not. Laravel is just framework so it allows you to complete some every-day actions in easier and faster way.
Obviously Laravel follows OOP and there is no something like "OOP syntax" as far as I know. But there are parts in Laravel and every other framework that are not so obvious.
Basically in middleware you have access to Request and closure. You can read about middleware here. If you want to dig in dipper you can look at \Illuminate\Pipeline\Pipeline class - there is carry method in there that is responsible for looping over each used middleware.

Laravel 5.0 custom 404 does not use middleware

I'm using a middleware to parse the output of the templates. This is working fine for all pages.
However when I want to show a 404 (got a custom page for that) it doesn't treat it as a http request (that's what I think) since it doesn't go through the middleware.
My question is, how to have ALL requests go through the middleware.
The error pages don't go through the routes.php.
In Kernel.php move your middleware from the $routeMiddleware array to $middleware array.
Middleware in this array will run on every request (tested in 5.1).
For people like me who spending hours in 2020 because of this weird behaviour...
Now in Laravel there is a new instrument «Fallback Routes».
Add this to /routes/web.php:
Route::fallback(function () {
return view("404"); // template should exists
});
After that all requests will go throught middlewares.
At Laravel 5.4 and probably some older ones you can modify the file app/exceptions/Handler.php and the function render like this:
if( is_a( $exception, \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class ) ) {
return redirect()->route( 'error_404' );
}
// ... old code in the function ...
in this way every 404 errors are redirected to certain real route that acts like other routes of site.
You may also submit any data from current request to show a reasonable error at the target.
I had a use case where api routes needs to always return json response.
All routes return json response (laravel checks through $request->expectsJson()) IF user specifically ask for it by sending accept: application/json header.
But many a times user doesn't send the header and they get an html response instead.
As for my use case, all api routes will always send json response we can force the routes to return json, by manually attaching accept: application/json header using a middleware.
App\Http\Middleware\ForceJsonOnAPIs.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Str;
class ForceJsonOnAPIs
{
/**
* Handle an incoming request.
*
* #param Request $request
* #param Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
// Force Json accept type on api routes
if ($request->is('api/*') && !Str::contains($request->header('accept'), ['/json', '+json'])) {
$request->headers->set('accept', 'application/json,' . $request->header('accept'));
}
return $next($request);
}
}
Register the middleware in App\Http\Kernel.php
// add the new middleware ForceJsonOnAPIs
protected $middleware = [
\App\Http\Middleware\ForceJsonOnAPIs::class,
// rest of the middleware,
];
Important: You can assign the middle to $middlewareGroups in Kernel like web or api, But you will get into trouble when 404 exception occurs. The issue is The error pages don't go through the routes.php (Thanks to #Björn Answer above) thus the routes middleware won't get called and the 404 will return html response.
It's the same case for validation or authentication exceptions.
In my opinion, it's best to assign the middleware in the $middleware array as it runs on each request. This way all exceptions will automatically return correct exceptions as json in all routes.

Resources