Override exception in handler - laravel

I am using Route model binding using the id of a product and my route looks like this:
Route::get('product/{product}', ProductController#index');
I also have a custom ProductNotFoundException which I want to return when the route model binding only for this model fails to get the row (the row does not exist or is soft deleted.)
Any ideas how I could achieve that?
The two solutions I thought of are:
Remove Route Model binding (obviously :P)
Override the exception in the Exceptions/Handler
I chose to go with the second and do the following
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
*
* #return \Symfony\Component\HttpFoundation\Response
* #throws ProductNotFoundException
*/
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException && $exception->getModel() === 'App\Models\Product') {
throw new ProductNotFoundException();
}
return parent::render($request, $exception);
}
However, I am not sure whether is a good practice to throw an exception in the exception handler.
Can anyone see any other way or advice of the above solution?

Customize the binding resolution in RouteServiceProvider:
Route::bind('product', function ($value) {
$product = Product::find($value);
if ($product === null) {
throw new ProductNotFoundException();
}
return $product;
});
Explicit Model Binding

Related

laravel return exception as json instead of an html

I have an old Laravel app that is upgraded to v7. Currently exceptions are returned as an HTML page. But I would like them to be returned as json response. I understand that exceptions are handled in Exceptions\Handler.php:
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array<int, class-string<Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
});
}
}
How can I return the whole exception sack as json?
Edit: The app is old and was using a custom exception handler. Now after the upgrade to V.7 it would be nice to use the default exception handler so that exceptions are returned in json.
Edit 2:
When I use
return response()->json([
'exception' =>exception,
]);
I get an empty object. I don't want to only return the message of the exception but the whole exception stack.
You can override render function inside App\Exceptions\Handler.php
and you can read more about it https://laravel.com/docs/7.x/errors#render-method

Need to Handle Laravel Exception and, Logging details mismatch error

I have tried to handle the exceptions on Handler class in my laravel project
if(!env('APP_DEBUG', false)){
return view('errors.500');
} else {
return parent::render($request, $exception);
}
Errors are redirecting to my page. but in the login page user name or password mismatch also redirecting to that page.
in the login error need to redirect to the login page,not to the common error page. how can handle it?
i have using default laravel auth login.
this is my Handler.php file,
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* 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)
{
// Render well-known exceptions here
// Otherwise display internal error message
if(!env('APP_DEBUG', false)){
return view('errors.500');
} else {
return parent::render($request, $exception);
}
}
}
no need to check for 500 errors manually .If you want to show custom error message for 500 erorrs in production then publish default laravel views for errors
php artisan vendor:publish --tag=laravel-errors
This will generate views for errors in following path
resources/views/errors
To customize 500 erorrs .Edit following path
resources/views/errors/500.blade.php
Also make sure this will only show when
APP_DEBUG=false

Laravel get middleware for current route

How do you retrieve the middlewares for the current route?
I'm trying to set the exception handler to work differently based on if you're within a particular part of the website by checking if a middleware has been added to the route.
<?php
namespace App\Exceptions;
//use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Abrigham\LaravelEmailExceptions\Exceptions\EmailHandler as ExceptionHandler;
use Illuminate\Routing\Exceptions\InvalidSignatureException;
use Illuminate\Support\Facades\Route;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* #param \Throwable $exception
* #return void
*
* #throws \Exception
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* 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)
{
switch(true) {
case $exception instanceof \Illuminate\Session\TokenMismatchException:
// Redirect back if form invalid
return redirect()
->back()
->withInput($request->except($this->dontFlash))
->withErrors('The form has expired due to inactivity. Please try again');
break;
case $exception instanceof \Illuminate\Database\Eloquent\ModelNotFoundException:
// Redirect back with message if model not found error
$redirect = app('redirect');
// Check for different url to prevent redirect loop
if (request()->fullUrl() === $redirect->back()->getTargetUrl()){
// $currentRouteMiddleware = request()->route()->controllerMiddleware() returns empty array
// $currentRouteMiddleware = Route::current()->router->getMiddleware(); router is private
$response = $redirect->to(isset($currentRouteMiddleware['admin.user']) ? '/admin' : '/');
} else {
$response = $redirect->back();
}
return $response
->withInput($request->except($this->dontFlash))
->withErrors('That page could not be found. Please try again or report the broken link: ' . $request->getRequestUri());
break;
}
return parent::render($request, $exception);
}
}
If I dump the current route inside the router it shows the middleware array that I need to check against: dd(Route::current()) but there doesn't appear to be a way of accessing the current router e.g: $currentRouteMiddleware = Route::current()->router->getMiddleware();
There are a couple options based on what you're looking for.
If you want all the middleware aliases assigned to the route, you can use:
Route::current()->gatherMiddleware();
This does not expand assigned middleware groups, so the result from this might look something like:
[
'web',
'admin.user'
]
If you want all of the middleware classes assigned to the route, you can use:
Route::gatherRouteMiddleware(Route::current());
This will give you the classes, but it will not have the associated aliases or groups for the classes. The result from this might look like:
[
"App\Http\Middleware\EncryptCookies",
"Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse",
"Illuminate\Session\Middleware\StartSession",
"Illuminate\View\Middleware\ShareErrorsFromSession",
"App\Http\Middleware\VerifyCsrfToken",
"Illuminate\Routing\Middleware\SubstituteBindings",
"TCG\Voyager\Http\Middleware\VoyagerAdminMiddleware"
]
The Route::getMiddleware() in the Handler is simply a list of \App\Http\Kernel::$routeMiddleware.
dd(Route::getMiddleware());
array:7 [▼
"auth" => "App\Http\Middleware\Authenticate"
"auth.basic" => "Illuminate\Auth\Middleware\AuthenticateWithBasicAuth"
"guest" => "App\Http\Middleware\RedirectIfAuthenticated"
"admin" => "App\Http\Middleware\AdminMiddleware"
// ...
]
It is recommended to declare a constant (or global variable) and check whether the corresponding constant is declared in the Handler to check whether AdminMiddleware has passed.
// app/Http/Middleware/AdminMiddleware.php
public function handle($request, Closure $next)
{
// if ... return ...
define('__APP_IS_ADMIN', true);
return $next($request);
}
// app/Exceptions/Handler.php
public function render($request, Exception $e)
{
// ...
$response = $redirect->to(defined('__APP_IS_ADMIN') ? '/admin' : '/');
request()->route()->controllerMiddleware();
List middlewares applied in the current route inside App\Exceptions\Handler. Using in unauthenticated method.
/**
* Convert an authentication exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest(in_array('admin', $request->route()->middleware(), true) ? route('admin.login') : route('login'));
}
I used this to get the "admin" middleware and redirect the user to the correct login page.

How to catch errors and best practices?

I'm performing various tasks on an eloquent model.
e.g.
Flight Repository
function store($request){
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
This method is called from a controller:
public function store(FlightRepository $flight, $request){
$flight->store($request);
}
How should one approach potential errors? try/catch? Where should it be placed in the controller or repository? What would I catch anyway, what exception type?
According to Laravel 5.0 and above,
All the Exceptions thrown in any part of the Laravel App, the exceptions are catched inside the report() method of Exception/Handler.php file, like this:
UPDATED
Your Repo should throw an Exception like this:
class CustomRepository extends Repository
{
public function repoMethod($id)
{
$model = Model::find($id);
// Throw your custom exception here ...
if(!$model) {
throw new CustomException("My Custom Message");
}
}
}
And your Handler should handle the CustomException like this:
/**
* 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)
{
// Handle your exceptions here...
if($exception instanceof CustomException)
return view('your_desired_view')->with('message' => $exception->getMessage());
parent::report($exception);
}
Hope this helps!

get current request in 404 page

Hey I want to use the current request object as the facade not the static way($request not Request::) in a custom 404 blade file.
I don't know if I can hint about it to the error handler or is there a way to create that object?
Should/Could I do it via the Expections/Handler.php file?
I've found Here the following answer:
//Create a view and set this code in app/Exception/Handler.php :
/**
* Render an exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if($e instanceof NotFoundHttpException)
{
return response()->view('missing', [], 404);
}
return parent::render($request, $e);
}
//Set this use to get it working :
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Is this the right way to do it?
Yes, you can do it from the Handler. Inside the render() method:
if ($e instanceof NotFoundHttpException) {
return response()->view('your.view.name', $dataYouWantToPass);
}

Resources