Laravel 4 - Handling 404s With Custom Messages - php

According to Laravel 4 docs I can throw a 404 with a custom response:
App::abort(404, 'My Message');
I can then handle all of my 404s with a custom page:
App::missing(function($exception)
{
return Response::view('errors.missing', array(), 404);
});
How can I pass 'My Message' through to the view in the same way that the generic Laravel error page does.
Thanks!

You can catch your message through the Exception parameter
App::missing(function($exception)
{
$message = $exception->getMessage();
$data = array('message', $message);
return Response::view('errors.missing', $data, 404);
});
Note: The code can be reduced, I wrote it like this for the sake of clarity.

In Laravel 5, you can provide Blade views for each response code in the /resources/views/errors directory. For example a 404 error will use /resources/views/errors/404.blade.php.
What's not mentioned in the manual is that inside the view you have access to the $exception object. So you can use {{ $exception->getMessage() }} to get the message you passed into abort().

Related

render function in Handler.php not working Laravel 8

I want to return a JSON response instead of the default 404 error page when ModelNotFoundException occurs. To do this, I wrote the following code into app\Exceptions\Handler.php :
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
return parent::render($request, $exception);
}
However it doesn't work. When the ModelNotFoundException occurs, Laravel just shows a blank page. I find out that even declaring an empty render function in Handler.php makes Laravel display a blank page on ModelNotFoundException.
How can I fix this so it can return JSON/execute the logic inside the overriden render function?
In Laravel 8x, You need to Rendering Exceptions in register() method
use App\Exceptions\CustomException;
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->renderable(function (CustomException $e, $request) {
return response()->view('errors.custom', [], 500);
});
}
For ModelNotFoundException you can do it as below.
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
return response()->json(...);
});
}
By default, the Laravel exception handler will convert exceptions into an HTTP response for you. However, you are free to register a custom rendering Closure for exceptions of a given type. You may accomplish this via the renderable method of your exception handler. Laravel will deduce what type of exception the Closure renders by examining the type-hint of the Closure:
More info about the error exception
This code doesn't work for me (in Laravel 8.74.0):
$this->renderable(function (ModelNotFoundException$e, $request) {
return response()->json(...);
});
Don't know why, but ModelNotFoundException is directly forwarded to NotFoundHttpException (which is a part of Symfony Component) that used by Laravel and will ultimately triggers a 404 HTTP response. My workaround is checking the getPrevious() method of the exception:
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
if ($e->getPrevious() instanceof ModelNotFoundException) {
return response()->json([
'status' => 204,
'message' => 'Data not found'
], 200);
}
return response()->json([
'status' => 404,
'message' => 'Target not found'
], 404);
}
});
And then we will know that this exception come from ModelNotFoundException and return a different response with NotFoundHttpException.
Edit
This is why ModelNotFoundException thrown as NotFoundHttpException
This one is my Handler file:
use Throwable;
public function render($request, Throwable $exception)
{
if( $request->is('api/*')){
if ($exception instanceof ModelNotFoundException) {
$model = strtolower(class_basename($exception->getModel()));
return response()->json([
'error' => 'Model not found'
], 404);
}
if ($exception instanceof NotFoundHttpException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
}
}
This one is only for all request in API route. If you want to catch all request, so remove the first if.
Please note that by default Laravel emits a JSON representation of an exception ONLY when you send a request with the header parameter Accept: application/json! For all other requests, Laravel sends normal HTML rendered output.

Customizing Spatie Laravel-Permission Exception Message

I have added the Saptie Laravel Permission Package in a Laravel 5.8 API application. Every works fine and I get exception when a non admin user tries to access admin specific routes.
However the default exception is rendered as HTML 403 User does not have the right roles. Considering I am using this inside an API application, I would like to return my own custom message for such exceptions.
I tried checking if the auth()->user()->hasRole('admin') but still got the same default exception page. Here's my code
route
Route::post('products', 'ProductController#store')->middleware('role:super-admin|admin'); // create a new product
Controller method
if (auth()->user()->hasRole('admin')) {
// create & store the product
$product = Product::create($request->all())
// return new product
$responseMessage = 'Successful operation';
$responseStatus = 200;
$productResource = new ProductResource($product);
return response()->json([
'responseMessage' => $responseMessage,
'responseStatus' => $responseStatus,
'product' => $productResource
]);
} else {
return response()->json([
'responseMessage' => 'You do not have required authorization.',
'responseStatus' => 403,
]);
}
Why is my custom message not showing?
Because you are protecting your routes through the role middleware the UnauthorizedException will be thrown before your controller code is ever reached.
What you can do is use laravels exception handler render method and check the exception type and return your own response:
from the docs:
The render method is responsible for converting a given exception into
an HTTP response that should be sent back to the browser. By default,
the exception is passed to the base class which generates a response
for you. However, you are free to check the exception type or return
your own custom response
app/Exceptions/Handler.php
use Spatie\Permission\Exceptions\UnauthorizedException;
public function render($request, Exception $exception)
{
if ($exception instanceof UnauthorizedException) {
return response()->json([
'responseMessage' => 'You do not have required authorization.',
'responseStatus' => 403,
]);
}
return parent::render($request, $exception);
}

Laravel API receive JSON 404 not found

I'm developing a Laravel 5.6 API and I'm using Resources and Collections, Route Model Binding.
To show an item, I currently use following code in my controller:
public function show(Todo $todo)
{
TodoResource::withoutWrapping();
return new TodoResource($todo);
}
In the Exceptions > Handler.php I have the following:
public function render($request, Exception $exception)
{
// This will replace our 404 response with
// a JSON response.
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Resource not found'
], 404);
}
return parent::render($request, $exception);
}
This works perfectly when the item is found in the database. If the item is not in the database I get a (when using a browser):
"Sorry, the page you are looking for could not be found"
When using POSTMAN rest client, I'm getting
{
"message": "No query results for model [App\\Todo].",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException",
....
....
I would like to simply retrieve a 404 error with text "Resource not found", using both a browser or POSTMAN.
* Update with Routing info *
In my api.php, I have the following:
Route::apiResource('todos', 'TodoController');
Route::fallback(function () {
return response()->json(['message' => 'Not Found!'], 404);
});
In web.php, I have:
Route::Resource('todos', 'TodoController');
What is the best way to achieve this?
Make sure to alias the exception class you are checking for.
use Illuminate\Database\Eloquent\ModelNotFoundException;
Without this you are checking for an instance of App\Exceptions\ModelNotFoundException.

How to catch authorization errors in Laravel's Handler render

I'm trying to show a custom error page, which I'd like to appear if the error wasn't a 'page not found' or a authentication issue (e.g. trying to access a page which the user doesn't have access to). I'm using the code below in Laravel 5.3's Handler.php. While the 404 part works, the authentication part doesn't (triggering this error just returns the 500 page instead). What am I missing?
public function render($request, Exception $e)
{
if ($e instanceof NotFoundHttpException || $e instanceof AuthorizationException || $e instanceof AuthenticationException) {
return parent::render($request, $e);
}
else {
return response()->view('errors.500', [
'sentryID' => $this->sentryID,
], 500);
}
}
Edit : Looks like you want to handle all the global error pages. Laravel uses symfony's exception handler to generate the error page text and style. This can be found at
vendor/symfony/debug/ExceptionHandler.php
It's used in vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php as
use Symfony\Component\Debug\ExceptionHandler as SymfonyExceptionHandler;
To handle every error and exception you can extend the method prepareResponse to app/Exceptions/Handler.php and make appropriate changes.
protected function prepareResponse($request, Exception $e)
{
if ($this->isHttpException($e)) {
return $this->toIlluminateResponse($this->renderHttpException($e), $e);
} else {
return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
}
}
You can check the underlying working of this method in vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
End edit
You don't need to mess in the render method for this. Out of the box laravel searches for error views and renders them if available based on the error code. So for 404 and 500 you could just create the following two views and customize it in there.
resources/views/errors/404.blade.php
resources/views/errors/500.blade.php
This views get the exception, status and header information for you to display if needed. They are called like so
return response()->view("errors.{$status}", ['exception' => $e], $status, $e->getHeaders());
For the authentication check. Laravel calls the unauthenticated method in app/Exceptions/Handler.php when a user is unauthenticated. This code by default redirects the users to login page or shows a json response. You can make you changes here.
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('login');
}

How to return a JsonModel error from a dispatch event in Zend Framework 2?

WHAT I'M TRYING TO DO
I have a PHP application using the Zend Framework 2 and it has a "visual" side (where it returns nice HTML web pages) and an API side (where it simply returns JSON). When a request is sent to our server it's routed through the onBootstrap( \Zend\Mvc\MvcEvent ) function in Module.php. The bootstrap function does some general setup and checks (is the user signed in...etc.) and then attaches a dispatch function for \Zend\Mvc\Controller\AbstractRestfulController's (our API controllers) and another dispatch function for \Zend\Mvc\Controller\AbstractActionController(our "Visual" controllers).
In the dispatch functions more session specific stuff is checked and setup and occasionally an error can occur. If an error occurs in the \Zend\Mvc\Controller\AbstractActionController's dispatch function then it simply returns the error message to the view and the view displays it to the user. If an error occurs in the \Zend\Mvc\Controller\AbstractRestfulController's dispatch function I want it to return a JsonModel with the error information and an appropriate response header (400,404...etc).
WHAT'S HAPPENING
When an error occurs in the \Zend\Mvc\Controller\AbstractRestfulController's dispatch function the response header is set but the body isn't. On top of that the action is still routed to the controller so if the controller specifies a new response header then that overrides the previous one.
Here's an excerpt of my code:
public function onBootstrap( \Zend\Mvc\MvcEvent $event ) {
...
$event_manager = $event->getApplication()->getEventManager();
$shared_manager = $event_manager->getSharedManager();
...
// Dispatch event for AbstractRestfulController calls
$shared_manager->attach('Zend\Mvc\Controller\AbstractRestfulController', 'dispatch', function($event) {
...
try {
$organization = $organization_interface->get($id);
} catch(Exception $e) {
$event->getResponse()->setStatusCode($e->getCode());
return new JsonModel(array(
'error' => $e->getMessage(),
));
}
...
}, 100);
...
}
So I know that the returned JsonModel won't work because it's being returned from the dispatch function and not the controller. What I want to know is an "easy" way to send my JsonModel as the response AND stop the framework from running the routed action in the controller.
Any and all help is appreciated. Thanks!
If you are looking for the string to be returned from that JSON Model then this should be enough -
It is obvious to get the action getting dispatch and not the JSON Model, so try this -
instead of -
return new JsonModel(array(
'error' => $e->getMessage(),
));
write
$view = new JsonModel(array(
'error' => $e->getMessage(),
));
echo $view->serialize();
http_response_code($e->getCode()); //Added the line of code as per suggested in the comment by B1NARY
exit();
This will return the JSON string.
Let us know if this is not what you are looking for.
This is, for me, a better zf2ish solution :
$this->response->setStatusCode(Response::STATUS_CODE_401);
$viewModel = new JsonModel(['error' => 'Unauthorized', 'error_description' => $exception->getMessage()]);
$event->setViewModel($viewModel);
$event->stopPropagation(true);
return $viewModel;

Categories