Access Request Object in Error Handler Laravel - php

In my Laravel 5.2 project, I have a middleware happily storing requests and responses to DB or files.
There I serialize/json_encode $request object for logging everything going on. (cookies, input, files, headers...)
I need to create an error handler that will use whole request object to include everything about request into the report email. But ExceptionHandler::report() does not accept Request as a parameter.

Laravel 5.2 provides the helper method request(), which works for this use case:
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $exception)
{
$request = request();
parent::report($exception);
}

In App\Exceptions\Handler.php and the render method wich does have the request as parameter.
here you could fire an event to store the stuff in a session or database.
For example:
public function render($request, Exception $e)
{
if ($e instanceof HttpException) {
if ($e->getStatusCode() == 403) {
Event::fire(new UserNotAllowed($request));
return redirect()->to("/home");
}
if ($e->getStatusCode() == 404) {
if (Auth::guest()) {
return redirect()->to("/");
}
}
}
if ($e instanceof ModelNotFoundException) {
$e = new NotFoundHttpException($e->getMessage(), $e);
}
return parent::render($request, $e);
}
more info here.

Related

Laravel not reporting all exceptions to report method of Handler.php

I added a send email function to the report method of app/Exceptions/Handler.php
But some exceptions the method is called and others not, the $dontReport array is empty
This error above, for example, is not being reported.
Here is the handler
class Handler extends ExceptionHandler
{
protected $dontReport = [
//
];
public function report(Throwable $exception)
{
if ($this->shouldReport($exception)) {
$this->sendEmail($exception);
}
parent::report($exception);
}
public function sendEmail(Throwable $exception)
{
try {
$e = FlattenException::create($exception);
$handler = new HtmlErrorRenderer(true);
$css = $handler->getStylesheet();
$content = $handler->getBody($e);
\Mail::send('emails.exception', compact('css', 'content'), function ($message) {
$message->to(['email#myemail.com'])
->subject('Exception: ' . \Request::fullUrl());
});
} catch (Throwable $exception) {
Log::error($exception);
}
}
}
Laravel automatically filters out certain types of exceptions automatically.
Behind the scenes, Laravel already ignores some types of errors for you, such as exceptions resulting from 404 HTTP "not found" errors or 419 HTTP responses generated by invalid CSRF tokens
Try updating your report method to this, this should send an email for every exception, regardless of the type.
Link to the docs for reporting exceptions.
public function report(Throwable $exception)
{
$this->sendEmail($exception);
parent::report($exception);
}

Laravel FatalErrorException but code seems ok

I'm dealing with laravel 5.0 throwing a FatalErrorException with no useful description.
We have a web app developed in laravel 5.0, with a small part in angularJS, and I work on an macbook air 2015.
A branch of the git project doesn't seem to work on my machine, although the other branches work just fine, and there's been no changes in configuration and no big changes in the code either. Also, it works on my colleague's machine.
Here's the returned error message:
local.ERROR: Symfony\Component\Debug\Exception\FatalErrorException:
Uncaught TypeError: Argument 1 passed to App\Exceptions\Handler::report() must be an instance of Exception,
instance of ParseError given, called in Applications/XAMPP/htdocs/myApp/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php on line 73 and defined in /Applications/XAMPP/htdocs/myApp/app/Exceptions/Handler.php:36
Here's Handler.php
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
use Tymon\JWTAuth\Exceptions\TokenBlacklistedException;
use Flash;
use Response;
class Handler extends ExceptionHandler {
/**
* A list of the exception types that should not be reported.
*
* #var array
*/
protected $dontReport = [
'Symfony\Component\HttpKernel\Exception\HttpException'
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $e
* #return void
*/
public function report(Exception $e)
{
parent::report($e);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if ($e instanceof ModelNotFoundException) {
if ($request->ajax()) {
return Response::json(null, 403);
}
$modelName = str_replace("No query results for model [App\Models\\", "", $e->getMessage());
$modelName = str_replace("].", "", $modelName);
Flash::error(trans(lcfirst($modelName) . '.not_found'));
return redirect()->back();
}
if ($e instanceof TokenBlacklistedException) {
return response()->json(['error' => 'token_blacklisted', 'description' => 'descrizione errore token blacklisted'], $e-> getStatusCode());
}
if ($e instanceof TokenInvalidException) {
return response()->json(['error' => 'token_invalid', 'description' => 'descrizione errore token invalid'], $e->getStatusCode()) ;
}
if ($e instanceof TokenExpiredException) {
return response()->json(['error' => 'token_expired', 'description' => 'descrizione errore token expired'], $e->getStatusCode()) ;
}
if ($this->isHttpException($e)) {
return $this->renderHttpException($e);
}
return parent::render($request, $e);
/*
if ($this->isHttpException($e))
{
return $this->renderHttpException($e);
}
else
{
return parent::render($request, $e);
}
*/
}
}
I'm used at seeing this type of generic message whenever I forget an import, but this doesn't seem to be case. I suspect I'm missing something in the configuration.
The error occurs on every page but the login page and the part in angular.
As soon as I log in and I get redirected to the homepage it crashes.
Any help would be appreciated.

Laravel : How to handle Exception in View composer

In my laravel 5.5 project, view composers are used for passing data to the views.
In the view composer's constructor() a try catch block is used to catch the exceptions and a custom exception is rethrown from the catch method.
In the default exception handler of the application, custom exception is handled to display my custom error view.
Problem : The custom exception is not working properly when thrown from the view composer. Laravel's default exception error page is shown instead of my custom error page.
ProductComponentComposer.php
namespace App\Http\ViewComposers;
use Illuminate\View\View;
use App\Repositories\ProductRepository;
use Exception;
use App\Exceptions\AppCustomException;
class ProductComponentComposer
{
protected $products;
/**
* Create a new product partial composer.
*
* #param ProductRepository $productRepo
* #return void
*/
public function __construct(ProductRepository $productRepo)
{
try {
$this->products = $productRepo->getProducts();
} catch (Exception $e) {
throw new AppCustomException("CustomError", 1001);
}
}
/**
* Bind data to the view.
*
* #param View $view
* #return void
*/
public function compose(View $view)
{
$view->with(['productsCombo' => $this->products]);
}
}
Handler.php
public function render($request, Exception $exception)
{
if($exception instanceof AppCustomException) {
//custom error page when custom exception is thrown
return response()->view('errors.app-custom-exception', compact('exception'));
}
return parent::render($request, $exception);
}
Note : The custom exception is handled properly if thrown from the controller.
I also tried throwing the exception from the compose() method of the ProductComponentComposer instead of the __constructor(). But that also not working.
How to fix this to get my custom exception view if any exception is occured in the view composer?
Thanks in advance..
I had the same issue where a custom exception was thrown within a method in my view composer class, yet \ErrorException is what I got displayed.
There's a handler on a framework's level (\laravel\framework\src\Illuminate\View\Engines\PhpEngine.php:45) I believe is causing this.
Fix I've applied:
App\Exceptions\Handler.php
public function render($request, Exception $exception)
{
if ($exception instanceof AppCustomException ||
$exception instanceof \ErrorException &&
$exception->getPrevious() instanceof AppCustomException
) {
//custom error page when custom exception is thrown
return response()->view('errors.app-custom-exception', compact('exception'));
}
// default
return parent::render($request, $exception);
}
Make sure that what you're getting really is an instance of \ErrorException

How to return 403 response in JSON format in Laravel 5.2?

I am trying to develop a RESTful API with Laravel 5.2. I am stumbled on how to return failed authorization in JSON format. Currently, it is throwing the 403 page error instead of JSON.
Controller: TenantController.php
class TenantController extends Controller
{
public function show($id)
{
$tenant = Tenant::find($id);
if($tenant == null) return response()->json(['error' => "Invalid tenant ID."],400);
$this->authorize('show',$tenant);
return $tenant;
}
}
Policy: TenantPolicy.php
class TenantPolicy
{
use HandlesAuthorization;
public function show(User $user, Tenant $tenant)
{
$users = $tenant->users();
return $tenant->users->contains($user->id);
}
}
The authorization is currently working fine but it is showing up a 403 forbidden page instead of returning json error. Is it possible to return it as JSON for the 403? And, is it possible to make it global for all failed authorizations (not just in this controller)?
We managed to resolve this by modifying the exceptions handler found in App\Exceptions\Handler.php adding it in the render function.
public function render($request, Exception $e)
{
if ($e instanceof AuthorizationException)
{
return response()->json(['error' => 'Not authorized.'],403);
}
return parent::render($request, $e);
}
Yes, make a simple before method in your policy which will be executed prior to all other authorization checks,
public function before($user, $ability,Request $request)
{
if (!yourconditiontrue) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return abort('403');
}
}
}
You can intercept the exception
try {
$this->authorize('update', $data);
} catch (\Exception $e)
{
return response()->json(null, 403);
}
As for the latest version of Laravel, as of now version >=7.x,
Generally setting request headers 'Accept' => 'application/json' will tell Laravel that you expect a json response back.
For errors you need to also turn off debugging by setting the APP_DEBUG=false on your .env file, which will make sure the response is json and no stacktrace is provided.
The accepted answer works, but if you don't want to return json for every route you can handle this with middleware.
A brief outline of how to do this:
Create an ApiAuthorization class and extend your main auth class.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Auth\Access\AuthorizationException;
class ApiAuthorization extends Authorize
{
public function handle($request, Closure $next, $ability, ...$models)
{
try {
$this->auth->authenticate();
$this->gate->authorize($ability, $this->getGateArguments($request, $models));
} catch (AuthorizationException $e) {
return response()->json(['error' => 'Not authorized.'],403);
}
return $next($request);
}
}
Add the middleware to $routeMiddleware in App\Http\Kernel.php
'api.can' => \App\Http\Middleware\ApiAuthorization::class,
Update your route. You can now use your new api auth middleware by calling api.can similar to the example in the docs
Route::get('tenant', [
'as' => 'api.tenant',
'uses' => 'TenantController#show'
])->middleware('api.can:show,tenant');
This method allows you to return json for specific routes without modifying the global exception handler.
I have also face the same issue in Laravel version 7.3 where the AuthorizationException is not caught. What I come to know that we have to include AuthorizationException in the Handler.php like
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\Access\AuthorizationException;
use Throwable;
use Exception;
use Request;
use Response;
class Handler extends ExceptionHandler
{
// ...
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Throwable $exception
* #return \Symfony\Component\HttpFoundation\Response
*
* #throws \Throwable
*/
public function render($request, Throwable $exception)
{
if ($exception instanceof AuthorizationException)
{
return response()->json(['message' => 'Forbidden'], 403);
}
if ($exception instanceof ModelNotFoundException && $request->wantsJson()) {
return response()->json(['message' => 'resource not found')], 404);
}
return parent::render($request, $exception);
}
// ...
}
FYI if you just add the AuthorizationException by using the following statement
use AuthorizationException;
It still not working. So we have to specify the fully qualified namespace path.

Laravel Exceptions Handler not work

Environment: Laravel 5.1, PHP 5.6.10
I tried to implement App\Exceptions\Handler::render() to response error message in JSON format.
The app/Exceptions/Handler.php as follows:
// ignore..
public function render($request, Exception $e)
{
if ($e instanceof ModelNotFoundException) {
$e = new NotFoundHttpException($e->getMessage(), $e);
} elseif ($e instanceof AbstractException) {
return response()->apiJsonError(
$e->getMessage(),
$e->getErrors(),
$e->statusCode());
}
// ignore...
}
In controller, it also throws an exception:
if (ArrayUtil::isIndexExceed($list, $maxIndex)) {
// Index exceeds
throw new App\Exceptions\ExceedingIndexException;
}
However, when the error occurs, the Handler::render() is not invoked. The response is the ExceedingIndexException stack.
The following part is Exception class
My custom exception class, ExceedingIndexException:
namespace App\Exceptions;
use App\Http\Responses\Error;
use App\Exceptions\AbstractException;
class ExceedingIndexException extends AbstractException
{
public function __construct()
{
$message = 'Unable to execute';
$error = new Error('exceeding_index_value');
$statusCode = 400;
parent::__construct($statusCode, $error, $message);
}
}
The ExceedingIndexException class inherits AbstractException:
namespace App\Exceptions;
abstract class AbstractException extends \Exception
{
protected $statusCode;
protected $errors;
public function __construct(
$statusCode, $errors, $message, $code = 0, \Exception $previous = null) {
parent::__construct($message, $code, $previous);
$this->statusCode = $statusCode;
$this->errors = $errors;
}
public function getStatusCode()
{
return $this->statusCode;
}
public function getErrors()
{
return $this->errors;
}
}
Solution
I found my project depends on Dingo API for RESTful API. Because, Dingo also supports and registers its own exception handler, App\Exceptions\Handler doesn't be invoked.
I tried to use Custom Exception Responses in Dingo API as my Exception Handler to response error in JSON format. And it works for me.
Actually, you can replace the Dingo API error handler by a custom error handler. Get a copy of https://github.com/KIVagant/api/blob/develop/src/Exception/Handler.php
and save it to YourApp\Exceptions\Api\V1\Handler.php. Add interface Dingo\Api\Contract\Debug\ExceptionHandler to it,
then follow the instruction in Exceptions now can return any additional data.
use Dingo\Api\Contract\Debug\ExceptionHandler as DingoExceptionHandler;
class Handler implements ExceptionHandler, DingoExceptionHandler {
Replace the error handler, eg in boot ().
// Resolve YourApp\Exceptions\Api\V1\Handler ifself
$this->app->alias('api.exception', 'YourApp\Exceptions\Api\V1\Handler');
$this->app->singleton('api.exception', function ($app) {
return new \YourApp\Exceptions\Api\V1\Handler($app['Illuminate\Contracts\Debug\ExceptionHandler'],
$app['config']['api.errorFormat'], $app['config']['api.debug']);
});
Don't forget set error format. You can set up error format in boot(), eg:
// Set up error format
$this->app['api.exception']->setErrorFormat(...)
Indeed, Dingo API took over the Exception handling.
You you face similar problems where Lumen Exception handler isn't handling anything, probably some package took over the handling. In this case it was Dingo API.
Register your custom response for the exception for Dingo API:
https://github.com/dingo/api/wiki/Errors-And-Error-Responses#custom-exception-responses

Categories