Catch exception at root - php

I'm learning Symfony5 and I was wondering whether it is possible to catch exceptions after route's execution.
The main idea is to get rid of some recurrent try-catch blocks of code in my routes:
public function getStudentMean(int $studentId): Response
{
try {
$mean = $this->gradedStudentService->getMean($studentId);
return new Response($mean, Response::HTTP_OK);
} catch (AbstractApiException $e) {
return $this->returnBadRequestResponse($e->getMessage());
}
}
public function deleteStudent(int $id): Response
{
try {
$this->studentService->deleteStudent($id);
return new Response('', Response::HTTP_NO_CONTENT);
} catch (AbstractApiException $e) {
return $this->returnBadRequestResponse($e->getMessage());
}
}
Do I have to edit my public\index.php file to catch the exception here? Is there another cleaner method to do so?
Thanks!

Yes, Symfony already have an integrated solution for that, in facts, Symfony catch every exception at root and let you manage them.
You can find information here.
How to do it
First, edit config/packages/framework.yaml and set a controller to manage all exceptions (property error_controller).
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
#http_method_override: true
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
#esi: true
#fragments: true
php_errors:
log: true
error_controller: App\Controller\ErrorController::showAction
When an exception is thrown, this controller will get as input the initial request and the thrown exception. Here is an example:
<?php
namespace App\Controller;
use App\Exceptions\ExpiredLinkException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Throwable;
/**
* Controller for exceptions
*/
class ErrorController extends AbstractCustomController
{
/**
* #param Request $request
* #param Throwable $exception
* #return Mixed
* #throws Throwable
*/
public function showAction(Request $request, Throwable $exception)
{
if ($exception instanceof HttpException) {
if ($exception->getStatusCode() == Response::HTTP_UNAUTHORIZED) {
return new RedirectResponse($_ENV['WEBSITE_BASE_URL'] . 'login?source=' . urlencode($request->getUri()));
}
}
if ($exception instanceof ExpiredLinkException) {
return $this->render('error/expired.html.twig');
}
if ($_ENV["APP_ENV"] == "prod") {
if ($exception instanceof HttpException) {
if ($exception->getStatusCode() == Response::HTTP_FORBIDDEN) {
return $this->render('error/403.html.twig');
}
if ($exception->getStatusCode() == Response::HTTP_NOT_FOUND) {
return $this->render('error/404.html.twig');
}
}
return $this->render('error/500.html.twig');
}
throw $exception;
}
}

Related

Jwt Laravel 8 Exception TokenBlacklistedException not working

Exception TokenBlacklistedException not working
I am using Laravel 8 and I a'm trying to use Exception: TokenBlacklistedException and TokenExpiredException but it does not work. returns the following error :
Tymon\JWTAuth\Exceptions\TokenBlacklistedException: The token has been blacklisted in file C:\Users\taha\Desktop\API-LARAVEL-8\Laravel-VUEJS\mynew-app\vendor\tymon\jwt-auth\src\Manager.php on line 109
#0 C:\Users\taha\Desktop\API-LARAVEL-8\Laravel-VUEJS\mynew-app\vendor\tymon\jwt-auth\src\Manager.php(128): Tymon\JWTAuth\Manager->decode(Object(Tymon\JWTAuth\Token))
#1 C:\Users\taha\Desktop\API-LARAVEL-8\Laravel-VUEJS\mynew-app\vendor\tymon\jwt-auth\src\JWT.php(106): Tymon\JWTAuth\Manager->refresh(Object(Tymon\JWTAuth\Token), false, false)
#2 C:\Users\taha\Desktop\API-LARAVEL-8\Laravel-VUEJS\mynew-app\app\Http\Middleware\JwtRefreshToken.php(44): Tymon\JWTAuth\JWT->refresh()
ion\Http\Middleware\TransformsRequest->handle(Object(Illuminate\Http\Request), Object(Clo...
<?php
namespace App\Http\Middleware;
use Closure;
//use Exception;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
use Tymon\JWTAuth\Exceptions\TokenBlacklistedException;
//use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
//use Symfony\Component\Debug\Exception\FatalErrorException;
use Tymon\JWTAuth\Facades\JWTAuth;
class JwtRefreshToken
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
//dd('Taha Suliman Ramadan');
try {
if (! $user = JWTAuth::parseToken()->authenticate()) {
return response()->json(['user_not_found'], 404);
}
} catch (TokenBlacklistedException $e) {
return response()->json(['token_expired'], status: 401);
}catch (TokenExpiredException $e) {
$newToken = JWTAuth::parseToken()->refresh();
return response()->json(['success'=>false,'token'=> $newToken,'message'=>'Token Expired (So Refreshed)...'], status:200);
} catch (TokenInvalidException $e) {
return response()->json(['token_invalid'], status: 401);
} catch (JWTException $e) {
return response()->json(['token_absent'], status: 401);
}
//---------------------------------
return $next($request);
}
}
Please help if any of you encountered a problem like this before
You can use instanceof in catch block. instanceof is used to determine whether a PHP variable is an instantiated object of a certain class:
try
{
//
}
catch (Exception $e)
{
if ($e instanceof TokenBlacklistedException)
{
//
}
elseif ($e instanceof TokenExpiredException)
{
//
}
elseif ($e instanceof TokenInvalidException)
{
//
}
...
}
Anyway, in Laravel, all exceptions are handled by the App\Exceptions\Handler class. Previous versions also said you can handle exceptions in app/Exceptions/Handler.php.

Symfony: Best place to catch exception (CQRS/DDD)

I've a personal application. I use design pattern CQRS/DDD for a API.
Schema:
User --> Controller (dispatch command) --> Command handler --> some services...
In my Rest API controller
$this->dispatch($cmd);
If a throw a exception in services or specification classes for example, ok, I've a listener to catch exception and create JSON response error.
But if I want to develop an interface module with TWIG, I think I will not use my listener because I don't want a JSON response.
Should I used try/catch in my controller of my new interface module ?
SomeController extends AbstractController
{
public function getObject($id)
{
try {
$this->dispatch($cmd);
catch(SomeException $ex) {
$this->render(....)
}
}
}
Where is the best place to catch exception for TWIG ?
Thanks.
Edit:
#Cid
if (some conditions && $form->handleRequest($request)->isValid()) --> My handler don't return bool or values.
Imagine this code. Imagine I want share a service between an API and web view app.
class ApiController
{
public function register()
{
$this->dispatch($cmd);
}
}
class WebController
{
public function register()
{
$this->dispatch($cmd);
}
}
class SomeHandler implements CommandHandlerInterface
{
/** #required */
public RegisterService $service;
public function __invoke(SomeCommand $command)
{
$this->service->register($command->getEmail())
}
}
class RegisterService
{
public function register(string $email)
{
// Exception here
}
}
So, I think the best place to handle Exception is EventSubscriber, see here: https://symfony.com/doc/current/reference/events.html#kernel-exception
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
public function onKernelException(ExceptionEvent $event)
{
$exception = $event->getThrowable();
$response = new Response();
// setup the Response object based on the caught exception
$event->setResponse($response);
// you can alternatively set a new Exception
// $exception = new \Exception('Some special exception');
// $event->setThrowable($exception);
}

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.

Access Request Object in Error Handler Laravel

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.

Categories