Catch not found error by certain route prefix [Laravel] - php

I'm building web api along side with website in laravel 5.2, when user visit unavailable url on my web like http://stage.dev/unavailable/link then it will throw on 404 page on errors view resource, then i want to catch with different way if user try to access my API with unavailable url like http//stage.dev/api/v1/unavailable/link, then i want to return json/xml response
{
'status' : 'not found',
'code' : 404,
'data' : []
}
not the view, is there a way to detect it by url prefix 'api/*' or how.., maybe another approach with similar result, so the device/client which access it could proceed by standard format (i have simple format in all json response)
{
'status' : 'success|failed|restrict|...',
'api_id' : '...',
'token' : '...',
'data' : [
{'...' : '...'},
{'...' : '...'},
...
]
}
SOLVED
I figure out something after read answer from Chris and Alexey this approach works for me i add couples of lines in handler.php at render() method,
if($e instanceof ModelNotFoundException || $this->isHttpException($e)) {
if($request->segment(1) == 'api' || $request->ajax()){
$e = new NotFoundHttpException($e->getMessage(), $e);
$result = collect([
'request_id' => uniqid(),
'status' => $e->getStatusCode(),
'timestamp' => Carbon::now(),
]);
return response($result, $e->getStatusCode());
}
}
my header request respond 404 error code and return json data like i want..

Maybe there's a better way to do that, but you could create custom error 404 handler. Follow this tutorial, but change case 404 part to something like this:
if(str_contains(Request::url(), 'api/v1/')){
return response()->json(your_json_data_here);
}else{
return \Response::view('custom.404',array(),404);
}

Inside App\Exception\Handler.php you have a render method which can be handy for general purpose error catching and handling.
In this case you can also use the request()->ajax() method to determine if it's ajax. It does this by checking that certain headers are present, in particular:
'XMLHttpRequest' == $this->headers->get('X-Requested-With')
Anyway, back to the render method in Handler.php.
You can do something like:
public function render($request, Exception $e)
{
if($e instanceof HttpException && $e->getStatusCode() == 404) {
if (request()->ajax()) {
return response()->json(['bla' => 'foo']);
} else {
return response()->view('le-404');
}
}
return parent::render($request, $e);
}

I figure out something after read answer from Chris and Alexey this approach works for me i add couples of lines in handler.php at render() method,,
if($e instanceof ModelNotFoundException || $this->isHttpException($e)) {
if($request->segment(1) == 'api' || $request->ajax()){
$e = new NotFoundHttpException($e->getMessage(), $e);
$result = collect([
'request_id' => uniqid(),
'status' => $e->getStatusCode(),
'timestamp' => Carbon::now(),
]);
return response($result, $e->getStatusCode());
}
}

Related

Carbon (laravel) deal with invalid date

I have a quite simple problem.. I use the Carbon::parse($date) function with $date = '15.15.2015'. Of course it can not return a valid string because there is no 15th month. But how can i "ignore" the error message?
Great would be something like
if (Carbon::parse($date) != error) Carbon::parse($date);
else echo 'invalid date, enduser understands the error message';
You can catch the exception raised by Carbon like this:
try {
Carbon::parse($date);
} catch (\Exception $e) {
echo 'invalid date, enduser understands the error message';
}
Later edit: Starting with Carbon version 2.34.0, which was released on May 13, 2020, a new type of exception is being thrown: Carbon\Exceptions\InvalidFormatException
So if you're using a newer version of Carbon, you can actually do this more elegantly
try {
Carbon::parse($date);
} catch (\Carbon\Exceptions\InvalidFormatException $e) {
echo 'invalid date, enduser understands the error message';
}
Thank you Christhofer Natalius for pointing this out!
Pass Laravel's validation before use it. Create a validator like this:
protected function validator(array $data)
{
//$data would be an associative array like ['date_value' => '15.15.2015']
$message = [
'date_value.date' => 'invalid date, enduser understands the error message'
];
return Validator::make($data, [
'date_value' => 'date',
],$message);
}
And call him right before use your date:
$this->validator(['date_value' => $date])->validate();
// $this->validator(request()->all())->validate(); you can pass the whole request if fields names are the same
Carbon::parse($date);
You can add all your desired fields to validator and apply multiple validations handling every message or using the default message. This would be the way if you are validating User's input
Try this
$birth_date = Carbon::createFromFormat('dmy', $birth_date_mark)->startOfDay();
if ($birth_date->format('dmy') !== $birth_date_mark) {
throw new OperationException(trans('exception.invalid_birth_date'));
}
another solution would be to handle the exception globally.
in app/Exceptions/Handler.php
use Carbon\Exceptions\InvalidFormatException;
then in the render method check if an exception is thrown by Carbon
if ($exception instanceof InvalidFormatException) {
return response()->json([
'status' => 'fail',
'data' => [
'message' => 'Invalid date formate!'
]
], 400);
}
Both try...catch... and validator is good. Sam's answer is good too.
In Laravel 8, I use following code:
// app/Exceptions/Handler.php
use Carbon\Exceptions\InvalidFormatException;
public function register()
{
$this->renderable(function (InvalidFormatException $e, $request) {
return response()->json([
'message' => 'Invalid date format!.'
], 400);
});
}
When InvalidFormatException thrown, Laravel will return http status 400 and json with message.
If you don't want to use try...catch... at every Carbon::parse() section, this is a good way to handle exception.

In Laravel, how to detect "422 Unprocessable Entity" with JSON response?

I am now trying to get the response from the server, if the server response 422 Unprocessable Entity, I could modify my function so that we can have different reponses to users.
Now there are 2 potential errors, the first one is
1) User doesn’t have enough funds or their credit card is declined for any reason
{"errors": ["The credit card on file could not be charged."]}
2) User is already subscribed to the product
{"errors": ["Cannot reactivate a subscription that is not marked \"Canceled\", \"Unpaid\", \"Trial Ended\", or \"On Hold\"."]}
the response is sent through JSON and I'm wondering what could I do in Laravel to detect these two problems? Appreciate your help, thanks.
I would use the Exceptions\Handler.php file to handle those exceptions properly.
public function render($request, Exception $e)
{
// 404 Errors
// Either the route does not exist or a model is not found when performing an Eloquent query
if($e instanceof NotFoundHttpException || $e instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Not found'
], 404);
} elseif ($e instanceof HttpException) {
return response()->json([
'error' => 'Unsupported Media Type'
], 415);
} elseif ($e instanceof AuthenticationException) {
return response()->json([
'error' => 'Forbidden. Unauthenticated.'
], 403);
} elseif ($e instanceof QueryException) {
return response()->json([
'error' => 'Unresolvable Query.',
'message' => $e->getMessage()
], 400);
}
return parent::render($request, $e);
}
As you can see you can catch the Exception and return a proper JSON response.
To better differentiate if you want to return JSON or a 'normal' message, you can check with if($request->ajax()) { ... } else { ... } if you request is an AJAX request and you want to return JSON or sth. else.

Slim3/DRY - How to handle errors/exceptions correctly without duplicating code?

I'm working on a fairly large JSON API using Slim3. My controllers/actions are currently littered with the following:
return $response->withJson([
'status' => 'error',
'data' => null,
'message' => 'Username or password was incorrect'
]);
At certain points in the application anything can go wrong and the response needs to be appropriate. But one thing that is common is the error responses are always the same. The status is always error, the data is optional (in the case of form validation errors data will contain those) and message is set to indicate to the user or consumer of the API what went wrong.
I smell code duplication. How can I reduce the code duplication?
From the top of my head all I could think of doing was creating a custom exception, something like App\Exceptions\AppException that takes option data and the message will be obtained form $e->getMessage().
<?php
namespace App\Exceptions;
class AppException extends Exception
{
private $data;
public function __construct($message, $data = null, $code = 0, $previous = null)
{
$this->data = $data;
parent::__construct($message, $code, $previous);
}
public function getData()
{
return $this->data;
}
}
Following that create middleware that calls $next wrapped in a try/catch:
$app->add(function($request, $response, $next) {
try {
return $next($request, $response);
}
catch(\App\Exceptions\AppException $e)
{
$container->Logger->addCritical('Application Error: ' . $e->getMessage());
return $response->withJson([
'status' => 'error',
'data' => $e->getData(),
'message' => $e->getMessage()
]);
}
catch(\Exception $e)
{
$container->Logger->addCritical('Unhandled Exception: ' . $e->getMessage());
$container->SMSService->send(getenv('ADMIN_MOBILE'), "Shit has hit the fan! Run to your computer and check the error logs. Beep. Boop.");
return $response->withJson([
'status' => 'error',
'data' => null,
'message' => 'It is not possible to perform this action right now'
]);
}
});
Now all I have to do at points in the code is to throw new \App\Exceptions\AppException("Username or password incorrect", null).
My only issue with this is it feels like I'm using exceptions for the wrong reasons and it may make debugging a little more difficult.
Any suggestions on reducing the duplicates and cleaning up error responses?
You can achieve similar similar results by creating an error handler which outputs JSON.
namespace Slim\Handlers;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
final class ApiError extends \Slim\Handlers\Error
{
public function __invoke(Request $request, Response $response, \Exception $exception)
{
$status = $exception->getCode() ?: 500;
$data = [
"status" => "error",
"message" => $exception->getMessage(),
];
$body = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
return $response
->withStatus($status)
->withHeader("Content-type", "application/json")
->write($body);
}
}
You must also configure Slim to use your custom error handler.
$container = $app->getContainer();
$container["errorHandler"] = function ($container) {
return new Slim\Handlers\ApiError;
};
Check Slim API Skeleton for example implemention.

StartSession Middleware throwing ErrorException

Using Laravel 5.2.
I am getting the following error when navigating to a route.
Argument 1 passed to Illuminate\Session\Middleware\StartSession::addCookieToResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given, called in /Users/.../vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php on line 72 and defined
What I don't understand is that if the request is made via POST, no ErrorException is thrown and the expected response is received.
I have modified the App\Exceptions\Handler::handler($request, Exception $e) method to the following;
public function render($request, Exception $e) {
// If request is being made via AJAX or wants a JSON response.
if ($request->ajax() || $request->wantsJson()) {
// Check to see if 'getStatusCode()' method exists, otherwise use 500.
$status_code = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500;
// Send JSON response.
return json($e->getMessage(), $status_code);
}
return parent::render($request, $e);
}
The json() helper is defined as:
function json($message, $status = 200, $data = null) {
$output = [
'success' => is_between($status, [200, 299]),
'statusCode' => $status,
'timestamp' => time(),
'message' => $message,
];
if(!is_null($data))
$output['data'] = $data;
return \Illuminate\Support\Facades\Response::json($output, $status);
}
and is_between();
function is_between($value, array $range) {
if($value > $range[1])
return false;
if($value < $range[0])
return false;
return true;
}
I have tried reverting App\Exceptions\Handler::handler($request, Exception $e) method back to see if any of that could be causing an issue, but I still get the same problem.
I have also tried rewriting the routes and the controller, with the same end result, works if made via a POST request, otherwise an ErrorException is thrown.
Somehow the framework is not converting the returned string into a response object. The addCookieToResponse method in the Illuminate\Session\Middleware\StartSession class is wanting a Response object as the first param. Make sure that you return one in all of your routes.
Here's a possible quick fix, change it to fit your case.
Before:
Route::get('hi', function() {
return 'hi';
});
After:
Route::get('hi', function() {
return response('hi');
});

Laravel Model Error Handling when Creating

I want to use Eloquent to create a DB entry like this:
MFUser::create(array(
'user_reference' => $this->userReference,
'first_name' => $this->firstName,
'last_name' => $this->lastName,
'user_type_id' => $this->userTypeId,
'email' => $this->email,
'password' => $this->password
));
It works well, except for the case, when exactly the same data is put into the fields, which is expected, as there should be no duplicate. I get the QueryExecption then.
But how do I properly perform error handling here to check if this query exception occurs so I can return a response from my server to the client via json then?
Just wrap that code in a try-catch block. Something like this:
try {
MFUser::create(array(
'user_reference' => $this->userReference,
'first_name' => $this->firstName,
'last_name' => $this->lastName,
'user_type_id' => $this->userTypeId,
'email' => $this->email,
'password' => $this->password
));
} catch (\Illuminate\Database\QueryException $exception) {
// You can check get the details of the error using `errorInfo`:
$errorInfo = $exception->errorInfo;
// Return the response to the client..
}
I prefer to reserve try/catch's for unexpected events that can't be handled elsewhere. In this case, you can utilise validation as a first measure, and the exception handler as a backup measure.
If the form request fails, the error messages are flashed and the user is returned to the previous page (where the form was submitted) and you can handle the messages gracefully. Validation requests
// First line of defence - gracefully handle
// Controller
public function store(MFUserRequest $request)
{
// The incoming request is valid...
MFUser::create(array(...));
}
// Form Request
class MFUserRequest extends Request
{
public function rules()
{
return [
'email' => 'required|email|unique:users,email',
];
}
}
Elsewhere, in your App\Exceptions directory you have the exception handler class that can be a catch all for various errors. Use this, when you haven't been able to gracefully handle it further down.
// Second line of defence - something was missed, and a model was
// created without going via the above form request
namespace App\Exceptions;
class Handler extends ExceptionHandler
{
public function render($request, Exception $e)
{
if($e instanceof QueryException) {
// log it or similar. then dump them back on the dashboard or general something bad
// has happened screen
return redirect()->route('/dashboard');
}
}
}
Simply make use of try / catch block.
use Illuminate\Database\QueryException;
// ...
try {
// DB query goes here.
} catch (QueryException $e) {
// Logics in case there are QueryException goes here
}
// ...
Try and Catch something might help u and imagine for create try catch for all of them, but have best practice to handle all of QueryException, my recommend is use Laravel Exceptions Handler because it's make u easy for make execption global!
let's try it!, open App\Exception\Handler.php and at render method u can write like this one
public function render($request, Throwable $exception)
{
if ($request->ajax() || $request->wantsJson()) {
if ($exception instanceof QueryException) {
// example algo for make response
return response()->json(['message' => 'xxx'], 403);
}
}
return parent::render($request, $exception);
}
after that, u can get xxx json for every error request triggered by Query

Categories