I am building api in laravel and I am catching exceptions if they occur and I am returning them as response. The problem is that I only want to show them in my dev enviroment. Setting APP_DEBUG=false doesn't solve the problem, error message is still in response.
public function foo()
{
try{
$this->bar();
}catch(\Exception $e){
return $this->error($e->getMessage(),'Something went wrong', 403);
}
}
This returns json for example:
{
"error": "No query results for model [App\\Models\\User].",
"message": "Something went wrong"
}
I want to have this in my dev enviroment, but on production I would like to have only the message like:
{
"error": "500 Server Error",
"message": "Something went wrong"
}
How do I achieve this and is this a good practice to do? I haven't found any solution yet, except to overwrite message if APP_ENV=production, but I have a feeling that there is a better way to do this.
protected function error($error, $message = '', $code = 400)
{
$this->response['error'] = $error;
$this->response['message'] = $message;
if(env('APP_ENV') === 'production'){
$this->response['message'] = '500 Server Error'
}
return response()->json($this->response, $code);
}
Agree with #dummy.learn's answer, the best way to handle exception is inside App\Exceptions\Handler, laravel's all exception will pass through App\Exceptions\Handler before it render or report, even those you didn't catched.
You can handle exception like this:
$this->renderable(function (Throwable $e, $request) {
return new ExceptionResource($e);
});
ExceptionResource is an API resources from Illuminate\Http\Resources\Json\JsonResource which you can see document from here: https://laravel.com/docs/9.x/eloquent-resources.
And I prefer to check enviroment from app helper not env or config instead, the helper will return a bool, that I think readability is better.
Example in ExceptionResource, showing error if environment is local dev or unit test:
public function toArray($request)
{
if(app()->environment(['local', 'testing']))
$error = $this->resource->getMessage();
else
$error = 'Server Error';
return [
'error' => $error,
'message' => 'Something went wrong'
];
}
You can catch it inside App\Exceptions\Handler
Use the render function to intercept all exceptions with json as response (for api only)
And within add check which env is it currently being deployed.
This is the only way to make sure your error overwrite will work only in production.
Also use config(app.env) === production is best practice instead of using env.
Now you can throw any kind (custom) exceptions , and all can be "filtered" by this code.
Extra note about config as best place as pointed out by #dbf (thanks mate)
This is based on how mr Otwell himself use config() instead of env(), in core and all of his laravel related oackages
dont forget also that config is cached, meaning faster in load time compare to raw env,
have a look at this link https://github.com/alexeymezenin/laravel-best-practices#do-not-get-data-from-the-env-file-directly ... As one of many recommended way to use config() instead of env()
Related
I've searched a few questions for a reason my code is not throwing an error correctly, but I can't figure it out.
I have the following function in my controller
<?php
public function suspend($id)
{
try {
$this->collection = $this->class::find($id);
$this->collection->delete();
return $this->respond_with_success();
} catch (\Exception $e) {
return $this->respond_with_error('Failed to suspend resource with id: ' . $id);
}
}
For reference, I'm using soft deletes. I can suspend a resource once no problem. If I try to suspend one that's already suspended, Laravel correctly throws a 500 as I can see in the log file /storage/logs/laravel.log
This is part of the error I see;
local.ERROR: Call to a member function delete() on null....
Without using
withTrashed() in the query, a row quite obviously cannot be found. So this makes sense.
Great...so why does my catch not actually catch anything? I see a 500 error in the browser, but my application should allow me to continue and handle that error correctly. But it just falls over completely...
The respond_with_error function is below. I've tried changing the $code to 200 in testing, but this doesn't change anything. I've tested returning a simple string rather than with this function to no avail, so I don't think there's anything wrong with this part.
<?php
protected function respond_with_error($message = 'error', $code = 500)
{
return Response::json([
'success' => false,
'message' => $message,
], $code);
}
I'm running Laravel 5.6.29
There are two ways to address this. The first thing to note is ERROR: Call to a member function delete() on null is not an exception, it is a fatal error.
You can use findOrFail instead of find to throw an Exception when the model is not found and that will work.
You could also catch Throwable instead of Exception to catch errors and exceptions (as of PHP7) or just Error to catch errors.
As the Error hierarchy does not inherit from Exception, code that uses catch (Exception $e) { ... } blocks to handle uncaught exceptions in PHP 5 will find that these Errors are not caught by these blocks. Either a catch (Error $e) { ... } block or a set_exception_handler() handler is required.
Read more on PHP7 Error Handling here: http://php.net/manual/en/language.errors.php7.php
This question already has answers here:
Disable Laravel's built-in error handling methods
(7 answers)
Closed 1 year ago.
In Laravel, whenever there error, even minor NOTICES, WARNINGS and DEPRECATED erros, I got the full debug info which kills the application. In my App.config I've turned debug => false and I get the message of 'Whoops, looks like something went wrong.'
How can I turn off all error handling but Laravel to just get normal PHP errors that do not interrupt the entire flow of application?
If you don't want to interrupt your workflow for certain PHP error types, you will need to disable the error handler registered by Laravel for those errors.
Laravel registers its error handling in Illuminate/Foundation/Bootstrap/HandleExceptions.php. This bootstrapper is one of several that is called when your Http kernel handles a request.
While there are a couple ways to do what you want to do, I think the easiest is to handle the event that is fired after this bootstrapper is called. In the event handler, you can reset the error handler for the errors you don't want Laravel to process.
In your bootstrap/app.php file, add the following line right before $app is returned:
$app->afterBootstrapping(
'Illuminate\Foundation\Bootstrap\HandleExceptions',
function ($app) {
set_error_handler(function ($level, $message, $file = '', $line = 0, $context = []) {
// Check if this error level is handled by error reporting
if (error_reporting() & $level) {
// Return false for any error levels that should
// be handled by the built in PHP error handler.
if ($level & (E_WARNING | E_NOTICE | E_DEPRECATED)) {
return false;
}
// Throw an exception to be handled by Laravel for all other errors.
throw new ErrorException($message, 0, $level, $file, $line);
}
});
}
);
The App\Exceptions\Handler.php file is built just for this.
In the public function render() method, you can catch applications and perform certain redirects/page views if you so choose:
For instance, you can capture HttpException's in your application and then return an error page if you wished:
public function render($request, Exception $e)
{
//other stuff
if ($e instanceof HttpException) {
return view('errors.general')->withErrors([
'message' => 'The application encountered an error!'
]);
}
return parent::render($request, $exception);
}
That up-voted answer is the opposite of what he asked.
As far as I can tell, there isn't a way to separate error reporting and laravel taking over rendering of the screen. I've been looking through the Laravel 5 code and haven't found a way to split them apart using their setup yet.
You could write your own library to totally take over all error handling and remove all of laravels internal tracking, but then you'd have to make sure to pass it back to laravel in the cases where you need the orig page error handling. Easiest way would be find a 3rd part error handler vendor then modify it to take over all error handlers and not block rendering.
Having installed Laravel and Bugsnag using the relevant documentation, I found that an NotFoundHttpException error for instance is not reported to Bugsnag (but notifyError yes). My question is how to set it so that all errors are reported, without using these lines over and over:
Bugsnag::notifyError('ErrorType', 'Something bad happened');
or
try {
// Some potentially crashy code
} catch (Exception $ex) {
Bugsnag::notifyException($ex);
}
I'm thinking of using the Handler in app/exceptions like so:
public function report(Exception $e)
{
Bugsnag::notifyException($e);
parent::report($e);
}
But if it's not mentioned in the Laravel/Bugsnag integration docs, is it a good practice? This Laracast video doesn't describe any changes to the exceptions handler and the setup seems to work as intended.
In App\Exceptions\Handler, remove all Exception classes from $dontReport. I'm not sure why you'd want to report all errors, but this should do it for you.
In
\app\Exceptions\Handler.php
overwrite internalDontReport property.
Below is a default that comes inherited from \vendor\laravel\framework\src\Illuminate\Foundation\Exceptions\Handler.php
protected $internalDontReport = [
AuthenticationException::class,
AuthorizationException::class,
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
TokenMismatchException::class,
ValidationException::class,
];
I'm currently working on an open source personal project that provides a nice backend api for game developers. I'm in the early stages of development, but I plan to write tests as I go along, which is where I've hit a snag.
Through out the system when an error occurs such as incorrect api credentials or missing credentials, I throw a custom exception which stores a bit of extra data so that I can catch it and give a JSON encoded response.
The tests work fine for those thrown in my BaseController, but I also capture a few Laravel Exceptions so I can respond with my own, or at least, output JSON like below:
app/start/global.php
App::error(function(Exception $exception, $code) {
Log::error($exception);
});
App::missing(function(Exception $exception) {
return BaseController::error(
Config::get('response.method.code'),
Config::get('response.method.http'),
'Method not found'
);
});
App::error(function(Viper\Exception $exception) {
return BaseController::error(
$exception->getCode(),
$exception->getStatusCode(),
$exception->getMessage()
);
});
I'm using the try { } catch() { } approach as I need to check an extra value that isn't in the normal Exceptions.
public function testNoMethodGET() {
$config = Config::get('response.method');
try {
$this->call('GET', '/');
} catch(\Viper\Exception $e) {
$this->assertEquals($e->getCode(), $config['code']);
$this->assertEquals($e->getStatusCode(), $config['http']);
}
$this->fail('Exception not thrown');
}
This is all good and well, but I want to check a few things on the actual response, like for example, whether or not the json is valid, whether or not the response structure matches and whether or not the response values are correct.
If I set the return value of $this->call() to a variable, I'd be unable to access that variable within the catch block, so the question is this, how can I test the return value of $this->call() once the Exception has been caught?
According to Taylor Otwell:
"this can be solved by de-coupling your
test. You really want to test the handler and that the exception is
thrown totally separately anyways [sic] to isolate your tests. For
instance:
App::error(function(ErrorType $e)
{
App::make('ErrorTypeHandler')->handle($e);
});
Now you can write test cases for ErrorTypeHandler class separately
from the rest of your application. Then check that proper exceptions
are thrown by your app with #expectedException."
see How do you test your App::error implementations?
In your case, you already have isolated your error handler in BaseController::error(), so you can test the responses directly in separate unit tests, without the use of $this->call(). Instead, just call $response = BaseController::error() with the desired parameters and then inspect the response and apply relevant assertions.
I have below code in error.php, which is triggered using App::abort(404, $error) in my controller. Still my response status code is 200(ok). I tried with various error codes like 400, 403
// NotFoundException handler
App::error(function(NotFoundException $e)
{
$default_message = 'The requested resource was not found';
return Response::json(array(
'error' => $e->getMessage() ?: $default_message,
), 404);
});
For anyone still googling this problem:
I was struggling with this problem for hours. For me the problem was caused by an issue with one of my controllers.
Check all of your controllers and make sure there are no spaces in front of the <?php tag. The <?php tag should be the very first thing in the file. A single space in front of the <?php tag in any of your controllers that are routed as such:
Route::controller('example', 'ExampleController');
Will cause all status codes to be 200.
I believe, regardless, you should receive a 404 response, so there might be something else happening that's the result of code not included in your question.
That being said, the Exception class that is thrown for 404 is NotFoundHttpException rather than NotFoundException.
Since Laravel 4 uses Symfony's HttpKernal, that Exception is here.
You can see here where App::abort() throws NotFoundHttpException when a 404 is triggered.
Therefore, your code should look like:
// NotFoundHttpException handler
App::error(function(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e)
{
$default_message = 'The requested resource was not found';
return Response::json(array(
'error' => $e->getMessage() ?: $default_message,
), 404);
});
Important: This will only fire for a 404 status, as that's the corresponding code to NotFoundHttpException. Other status codes return other Exception classes. To capture all HTTP status error codes exceptions, type hint for HttpException like so:
// HttpException handler
App::error(function(\Symfony\Component\HttpKernel\Exception\HttpException $e)
{
return Response::json(array(
'error' => $e->getMessage(),
), $e-> getStatusCode());
});
Lastly, consider using a bit of Content Negotiation when deciding to return JSON or HTML.
The solution didn't worked for me, so in case anyone is still looking for an answer, I thought it be best to put it here instead of creating another question.
After some time I had this problem too, in my app/Exceptions/Handler.php I had:
if ($e instanceof ModelNotFoundException) {
if ($request->ajax()) {
return response()
->json(['error' => ['No results']])
->header('status', 422);
}
}
This worked in my local environment, however, in the homolog environment (which reproduces the production environment, just to be clear) it didn't returned the correct status code.
After another look I started looking at Laravel's docs, and I changed the call to the following:
return response()
->json(['error' => ['No results.']], 422);
And that did the trick. Hope this can help.
In my case I found some space in front of <?php
Remove dump & other print functions
I was actively debugging when I noticed this issue. It was caused because I had dump(...) calls in the code at that time.
When I removed all my debug dump calls, the status code was correctly 404 again (using abort(404).