Using Laravel 5.2.
I am getting the following error when navigating to a route.
Argument 1 passed to Illuminate\Session\Middleware\StartSession::addCookieToResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given, called in /Users/.../vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php on line 72 and defined
What I don't understand is that if the request is made via POST, no ErrorException is thrown and the expected response is received.
I have modified the App\Exceptions\Handler::handler($request, Exception $e) method to the following;
public function render($request, Exception $e) {
// If request is being made via AJAX or wants a JSON response.
if ($request->ajax() || $request->wantsJson()) {
// Check to see if 'getStatusCode()' method exists, otherwise use 500.
$status_code = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500;
// Send JSON response.
return json($e->getMessage(), $status_code);
}
return parent::render($request, $e);
}
The json() helper is defined as:
function json($message, $status = 200, $data = null) {
$output = [
'success' => is_between($status, [200, 299]),
'statusCode' => $status,
'timestamp' => time(),
'message' => $message,
];
if(!is_null($data))
$output['data'] = $data;
return \Illuminate\Support\Facades\Response::json($output, $status);
}
and is_between();
function is_between($value, array $range) {
if($value > $range[1])
return false;
if($value < $range[0])
return false;
return true;
}
I have tried reverting App\Exceptions\Handler::handler($request, Exception $e) method back to see if any of that could be causing an issue, but I still get the same problem.
I have also tried rewriting the routes and the controller, with the same end result, works if made via a POST request, otherwise an ErrorException is thrown.
Somehow the framework is not converting the returned string into a response object. The addCookieToResponse method in the Illuminate\Session\Middleware\StartSession class is wanting a Response object as the first param. Make sure that you return one in all of your routes.
Here's a possible quick fix, change it to fit your case.
Before:
Route::get('hi', function() {
return 'hi';
});
After:
Route::get('hi', function() {
return response('hi');
});
Related
I want to return a json response when an api call is made to a laravel 5.7 app api route when the model is not found. To do this I have modified the render() method of app\Exceptions\Handler.php like this
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException && $request->wantsJson()) {
return response()->json(['message' => 'Not Found!'], 404);
}
return parent::render($request, $exception);
}
and my controller show() method is using a Book model like this
public function show(Book $book)
{
return new BookResource($book->load('ratings'));
}
Test on postman, a get call to localhost:8000/api/books/1 (id 1 has been deleted) keeps returning the default laravel 404 not found page instead of json.
Have I missed a step or something? I also noticed that adding a conditional statement inside the controller show() method like this
public function show(Book $book)
{
if ($book) {
return new BookResource($book->load('ratings'));
} else {
return response()->json(['message' => 'Not found'], 404);
}
}
returns the same html result instead of json.
What will be the proper way to handle this scenario?
Your code is correct. The problem is that you are probably testing it on a Local environment so in your .env you have set:
APP_DEBUG=true, switch it to APP_DEBUG=false and you will see your custom message.
PS: $request->wantsJson() is not necessary if your clients send the correct header info, eg: 'accept:application/json'
You can remove $request->wantsJson
or you can set the header in your request "Accept" => "application/json"
May this can help you:
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException && ($request->wantsJson() || $request->ajax())) {
return response()->json(['message' => 'Not Found!'], 404);
}
return parent::render($request, $exception);
}
I'm working on a fairly large JSON API using Slim3. My controllers/actions are currently littered with the following:
return $response->withJson([
'status' => 'error',
'data' => null,
'message' => 'Username or password was incorrect'
]);
At certain points in the application anything can go wrong and the response needs to be appropriate. But one thing that is common is the error responses are always the same. The status is always error, the data is optional (in the case of form validation errors data will contain those) and message is set to indicate to the user or consumer of the API what went wrong.
I smell code duplication. How can I reduce the code duplication?
From the top of my head all I could think of doing was creating a custom exception, something like App\Exceptions\AppException that takes option data and the message will be obtained form $e->getMessage().
<?php
namespace App\Exceptions;
class AppException extends Exception
{
private $data;
public function __construct($message, $data = null, $code = 0, $previous = null)
{
$this->data = $data;
parent::__construct($message, $code, $previous);
}
public function getData()
{
return $this->data;
}
}
Following that create middleware that calls $next wrapped in a try/catch:
$app->add(function($request, $response, $next) {
try {
return $next($request, $response);
}
catch(\App\Exceptions\AppException $e)
{
$container->Logger->addCritical('Application Error: ' . $e->getMessage());
return $response->withJson([
'status' => 'error',
'data' => $e->getData(),
'message' => $e->getMessage()
]);
}
catch(\Exception $e)
{
$container->Logger->addCritical('Unhandled Exception: ' . $e->getMessage());
$container->SMSService->send(getenv('ADMIN_MOBILE'), "Shit has hit the fan! Run to your computer and check the error logs. Beep. Boop.");
return $response->withJson([
'status' => 'error',
'data' => null,
'message' => 'It is not possible to perform this action right now'
]);
}
});
Now all I have to do at points in the code is to throw new \App\Exceptions\AppException("Username or password incorrect", null).
My only issue with this is it feels like I'm using exceptions for the wrong reasons and it may make debugging a little more difficult.
Any suggestions on reducing the duplicates and cleaning up error responses?
You can achieve similar similar results by creating an error handler which outputs JSON.
namespace Slim\Handlers;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
final class ApiError extends \Slim\Handlers\Error
{
public function __invoke(Request $request, Response $response, \Exception $exception)
{
$status = $exception->getCode() ?: 500;
$data = [
"status" => "error",
"message" => $exception->getMessage(),
];
$body = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
return $response
->withStatus($status)
->withHeader("Content-type", "application/json")
->write($body);
}
}
You must also configure Slim to use your custom error handler.
$container = $app->getContainer();
$container["errorHandler"] = function ($container) {
return new Slim\Handlers\ApiError;
};
Check Slim API Skeleton for example implemention.
I'm building web api along side with website in laravel 5.2, when user visit unavailable url on my web like http://stage.dev/unavailable/link then it will throw on 404 page on errors view resource, then i want to catch with different way if user try to access my API with unavailable url like http//stage.dev/api/v1/unavailable/link, then i want to return json/xml response
{
'status' : 'not found',
'code' : 404,
'data' : []
}
not the view, is there a way to detect it by url prefix 'api/*' or how.., maybe another approach with similar result, so the device/client which access it could proceed by standard format (i have simple format in all json response)
{
'status' : 'success|failed|restrict|...',
'api_id' : '...',
'token' : '...',
'data' : [
{'...' : '...'},
{'...' : '...'},
...
]
}
SOLVED
I figure out something after read answer from Chris and Alexey this approach works for me i add couples of lines in handler.php at render() method,
if($e instanceof ModelNotFoundException || $this->isHttpException($e)) {
if($request->segment(1) == 'api' || $request->ajax()){
$e = new NotFoundHttpException($e->getMessage(), $e);
$result = collect([
'request_id' => uniqid(),
'status' => $e->getStatusCode(),
'timestamp' => Carbon::now(),
]);
return response($result, $e->getStatusCode());
}
}
my header request respond 404 error code and return json data like i want..
Maybe there's a better way to do that, but you could create custom error 404 handler. Follow this tutorial, but change case 404 part to something like this:
if(str_contains(Request::url(), 'api/v1/')){
return response()->json(your_json_data_here);
}else{
return \Response::view('custom.404',array(),404);
}
Inside App\Exception\Handler.php you have a render method which can be handy for general purpose error catching and handling.
In this case you can also use the request()->ajax() method to determine if it's ajax. It does this by checking that certain headers are present, in particular:
'XMLHttpRequest' == $this->headers->get('X-Requested-With')
Anyway, back to the render method in Handler.php.
You can do something like:
public function render($request, Exception $e)
{
if($e instanceof HttpException && $e->getStatusCode() == 404) {
if (request()->ajax()) {
return response()->json(['bla' => 'foo']);
} else {
return response()->view('le-404');
}
}
return parent::render($request, $e);
}
I figure out something after read answer from Chris and Alexey this approach works for me i add couples of lines in handler.php at render() method,,
if($e instanceof ModelNotFoundException || $this->isHttpException($e)) {
if($request->segment(1) == 'api' || $request->ajax()){
$e = new NotFoundHttpException($e->getMessage(), $e);
$result = collect([
'request_id' => uniqid(),
'status' => $e->getStatusCode(),
'timestamp' => Carbon::now(),
]);
return response($result, $e->getStatusCode());
}
}
I tried to set the http status in my custom API when a request is being made.
protected $statusCode = 200;
public function setStatusCode($statusCode) {
$this->statusCode = $statusCode;
return $this;
}
public function respond($data, $headers = []) {
return response()->json($data, $this->getStatusCode(), $headers);
}
public function respondCreated($message) {
return $this->setStatusCode(201)->respond([
'message' => $message
]);
}
$this->respondCreated("Incident was created");
But when I make my server request in POSTMAN, I see status 200 and not 201 as set in the code above and the message is not appearing at all. Do I need to do it differently?
I am using the Laravel framework and implemented the functions by the book "Build APIs you won't hate"
I used the http_response_code() method as suggested and set the code like this:
public function respondCreated($message) {
$this->setStatusCode(201)->respond([
'message' => $message
]);
http_response_code(201);
return $this;
}
When I then return the response code it shows properly, but the POSTMAN Status is still 200?
The helper method by laravel is response() and is described as:
Returning a full Response instance allows you to customize the response's HTTP status code and headers. A Response instance inherits from the Symfony\Component\HttpFoundation\Response class, providing a variety of methods for building HTTP responses:
use Illuminate\Http\Response;
Route::get('home', function () {
return (new Response($content, $status))
->header('Content-Type', $value);
});
For convenience, you may also use the response helper:
Route::get('home', function () {
return response($content, $status)
->header('Content-Type', $value);
});
You can set the HTTP Response Code as stated on PHP documentation.
<?php
// Get the current default response code
var_dump(http_response_code()); // int(200)
// Set our response code
http_response_code(404);
// Get our new response code
var_dump(http_response_code()); // int(404)
?>
Im just move to laravel 5 and im receiving errors from laravel in HTML page. Something like this:
Sorry, the page you are looking for could not be found.
1/1
NotFoundHttpException in Application.php line 756:
Persona no existe
in Application.php line 756
at Application->abort('404', 'Person doesnt exists', array()) in helpers.php line
When i work with laravel 4 all works fine, the errors are in json format, that way i could parse the error message and show a message to the user. An example of json error:
{"error":{
"type":"Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException",
"message":"Person doesnt exist",
"file":"C:\\xampp\\htdocs\\backend1\\bootstrap\\compiled.php",
"line":768}}
How can i achieve that in laravel 5.
Sorry for my bad english, thanks a lot.
I came here earlier searching for how to throw json exceptions anywhere in Laravel and the answer set me on the correct path. For anyone that finds this searching for a similar solution, here's how I implemented app-wide:
Add this code to the render method of app/Exceptions/Handler.php
if ($request->ajax() || $request->wantsJson()) {
return new JsonResponse($e->getMessage(), 422);
}
Add this to the method to handle objects:
if ($request->ajax() || $request->wantsJson()) {
$message = $e->getMessage();
if (is_object($message)) { $message = $message->toArray(); }
return new JsonResponse($message, 422);
}
And then use this generic bit of code anywhere you want:
throw new \Exception("Custom error message", 422);
And it will convert all errors thrown after an ajax request to Json exceptions ready to be used any which way you want :-)
Laravel 5.1
To keep my HTTP status code on unexpected exceptions, like 404, 500 403...
This is what I use (app/Exceptions/Handler.php):
public function render($request, Exception $e)
{
$error = $this->convertExceptionToResponse($e);
$response = [];
if($error->getStatusCode() == 500) {
$response['error'] = $e->getMessage();
if(Config::get('app.debug')) {
$response['trace'] = $e->getTraceAsString();
$response['code'] = $e->getCode();
}
}
return response()->json($response, $error->getStatusCode());
}
Laravel 5 offers an Exception Handler in app/Exceptions/Handler.php. The render method can be used to render specific exceptions differently, i.e.
public function render($request, Exception $e)
{
if ($e instanceof API\APIError)
return \Response::json(['code' => '...', 'msg' => '...']);
return parent::render($request, $e);
}
Personally, I use App\Exceptions\API\APIError as a general exception to throw when I want to return an API error. Instead, you could just check if the request is AJAX (if ($request->ajax())) but I think explicitly setting an API exception seems cleaner because you can extend the APIError class and add whatever functions you need.
Edit: Laravel 5.6 handles it very well without any change need, just be sure you are sending Accept header as application/json.
If you want to keep status code (it will be useful for front-end side to understand error type) I suggest to use this in your app/Exceptions/Handler.php:
public function render($request, Exception $exception)
{
if ($request->ajax() || $request->wantsJson()) {
// this part is from render function in Illuminate\Foundation\Exceptions\Handler.php
// works well for json
$exception = $this->prepareException($exception);
if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
return $exception->getResponse();
} elseif ($exception instanceof \Illuminate\Auth\AuthenticationException) {
return $this->unauthenticated($request, $exception);
} elseif ($exception instanceof \Illuminate\Validation\ValidationException) {
return $this->convertValidationExceptionToResponse($exception, $request);
}
// we prepare custom response for other situation such as modelnotfound
$response = [];
$response['error'] = $exception->getMessage();
if(config('app.debug')) {
$response['trace'] = $exception->getTrace();
$response['code'] = $exception->getCode();
}
// we look for assigned status code if there isn't we assign 500
$statusCode = method_exists($exception, 'getStatusCode')
? $exception->getStatusCode()
: 500;
return response()->json($response, $statusCode);
}
return parent::render($request, $exception);
}
On Laravel 5.5, you can use prepareJsonResponse method in app/Exceptions/Handler.php that will force response as JSON.
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
return $this->prepareJsonResponse($request, $exception);
}
Instead of
if ($request->ajax() || $request->wantsJson()) {...}
use
if ($request->expectsJson()) {...}
vendor\laravel\framework\src\Illuminate\Http\Concerns\InteractsWithContentTypes.php:42
public function expectsJson()
{
return ($this->ajax() && ! $this->pjax()) || $this->wantsJson();
}
I updated my app/Exceptions/Handler.php to catch HTTP Exceptions that were not validation errors:
public function render($request, Exception $exception)
{
// converts errors to JSON when required and when not a validation error
if ($request->expectsJson() && method_exists($exception, 'getStatusCode')) {
$message = $exception->getMessage();
if (is_object($message)) {
$message = $message->toArray();
}
return response()->json([
'errors' => array_wrap($message)
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
By checking for the method getStatusCode(), you can tell if the exception can successfully be coerced to JSON.
If you want to get Exception errors in json format then
open the Handler class at App\Exceptions\Handler and customize it.
Here's an example for Unauthorized requests and Not found responses
public function render($request, Exception $exception)
{
if ($exception instanceof AuthorizationException) {
return response()->json(['error' => $exception->getMessage()], 403);
}
if ($exception instanceof ModelNotFoundException) {
return response()->json(['error' => $exception->getMessage()], 404);
}
return parent::render($request, $exception);
}