How can I access Laravel Response Object from within controller? - laravel

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.

Related

How to assert $next in Laravel Middleware

Lavarvel application running 7.10
I have a middleware that gets called on several routes. It check the user has the right relationships in place to get to the desired destination (that's not important).
I want a test that simply asserts that the user gets to the $next($request) in the middleware. I could of course just hit one of the end points, and assert something about that which would in turn validate the middleware, but all the routes hit external apis to get some other data which I could mock in my tests, which all seem pretty ugly just to test a middleware class.
I my test I can factory up the relevant relations etc, so whenever the middleware is called it should allow me straight in.
If I do this...
/** #test */
public function middleware_successful_test()
{
$user = factory(User::class)->create();
// some other factoried user relations here
$this->actingAs($user);
$request = new Request;
$middleware = app(MiddlewareClass::class);
$result = $middleware->handle($request, function ($req) {});
// don't know what assert here. $result is basically empty
}
Any tips, advise welcome.
Normally i would not unit test middlewares, but test it as a feature.
For your case i would utilize that you provide the next closure and you get the result. Return true and assert it is what is returned and therefor the closure has been called.
$result = $middleware->handle($request, function ($req) { return true;});
$this->assertSame(true, $result);

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

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

Laravel REST API - returning different content types based on accept header parameter

One of my endpoints require to return a PDF file. Normally it is JSON by default.
Do you think it is good to use if ($request->header('accept') === 'application/pdf') to return the PDF file? Does it break the single responsibility?
What if I have a lot of exceptions like that? It'd be too complex to have a lot of if/else statements for a lot of methods. What would be your suggestions?
You can create middleware and apply to a single endpoint or to a group of them. This would leave everything organized and easily manageable in the future.
set header for php with header function in that api at the last of response and for frontend side check header contentType on behalf of that do your remaining stuff
You have to make a middleware and and add this to kernel.php at protected $middleware array and do this code in 'handle' function
$response = $next($request);
if ($response instanceof ClassNameFromWhichObjectIsMatch )
return $response;
// add other headers
// $response->header($key, $value);
return $response;

Laravel 5.5 Request is empty in Restful controller

I have such a route in my routes/web.php
Route::resource('/api/surveys', 'SurveyController');
As documentation says, it creates all needed routes for API. This is a function, that gets executed when I go for /api/surveys route:
public function index()
{
$request = request();
if(!$request->hasHeader('token')) {
return "No auth token found.";
}
$tokenCheck = $this->userService->isTokenValid($request->header('token'));
if($tokenCheck !== true) {
return $tokenCheck;
}
return $this->surveyService->all();
}
What it does, it checks if token header parameter is set, if not, it returns an error, if yes, it checks if token is valid and etc. if everything is OK, it should return surveys from database.
public function surveys() {
$request = \Request::create('/api/surveys', 'GET');
$request->headers->set('Accept', 'application/json');
$request->headers->set('token', \Cookie::get('token'));
$response = \Route::dispatch($request);
print_r('<pre>');
print_r($response);
print_r('</pre>');
}
I have a website, that should use that API I just created to get all survey records. I create a new request object, set header "token" with token I get from a cookie and then try to dispatch and get a response. But the problem is that everytime I get "No auth token found." error. That means $request->hasHeader('token') returns false, even tough I set it here in my request. If I print_r $request->all() in Restful controller, I get an empty array.
I tried Postman to access this API with token parameter, and it works fine in postman, but here, it seems that Request disappears while it travels to API controller.
What I did wrong here?
When you manually create a request and dispatch it, that works to get the routing to call the correct controller, however that does not affect the request that is bound in the container.
When your "fake" request is handled by the api controller, the request that it pulls out of the container is the original "real" request that was made by the user.
Instead of dispatching the route with your new request, you will need to app()->handle($request) the new request. This, however, will completely replace the original "real" request with your new "fake" request, so everything from the original request will be lost.
Having said all that, this method of consuming your own api is discouraged, even by Taylor. You can read his comment on this Github issue. So, consuming your own api like this may work, but you may also run into some other unforeseen issues.
The more appropriate solution would be to extract out the logic called by the api routes to another class, and then call that extracted logic from both your api routes and your web routes.

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