How to catch server's error in inertiajs request? - laravel

In laravel 8 / inertiajs 0.10 / vue 3 I want to catch some error which happens on server, like :
this.form.post(this.route('ads.store'), {
preserveScroll: true,
onSuccess: (p) => { // On succdess I see this message
console.log('onSuccess p::')
console.log(p)
Swal.fire(
'New Ad',
'Your post has successfully been published!',
'success'
)
this.form.description = null
},
onError: (p) => { // That is not triggered!
console.log('onError p::')
console.log(p)
}
})
In control :
public function store( AdFormRequest $request) {
$data = $request->all();
$data['status']= 'A';
$ad = Ad::create($data);
return $request->wantsJson()
? new JsonResponse($ad, 200)
: back()->with('status', 'Error saving add');
}
So if one of required fields is empty I got laravel error popup window...
How to catch it and to show it in Swal.fire ?
MODIFIED # 1:
Searching in net I found onError property, but making :
this.deleteForm.delete(this.route('ads.destroy', this.nextAd), {
preserveScroll: true,
onError: (p) => { // THIS PART IS NOT CALLED ON ERROR
console.log('onError p::')
console.log(p)
Swal.fire(
'Delete Ad',
'Error deleting ad!',
'error'
)
},
onSuccess:()=>{
Swal.fire( // THIS PART IS CALLED ON SUCCESS
'Delete Ad',
'Your post has successfully been Delete!',
'success'
)
}
})
and in control :
public function destroy(Request $request, Ad $ad) {
try {
DB::beginTransaction();
$ad->deleTTTte();
DB::commit();
} catch (QueryException $e) {
DB::rollBack(); // I SEE THIS MESSAGE IN LOG FILE ON ERROR
\Log::info( '-1 AdController store $e->getMessage() ::' . print_r( $e->getMessage(), true ) );
return $request->wantsJson()
? new JsonResponse($ad, 500 /*HTTP_RESPONSE_INTERNAL_SERVER_ERROR*/ )
: back()->with('status', 'Error adding ad : ' . $e->getMessage());
return;
}
return $request->wantsJson()
? new JsonResponse($ad, HTTP_RESPONSE_OK)
: back()->with('status', 'Ad saved succesully');
}
Which way is correct?
Thanks!

The onError() callback is only called when there is a specific flag in the session called errors. So, in you server's catch block you need to flash the errors in the user's session and redirect to your calling page:
try {
// errors throw here
} catch {
return back()->with('errors', 'Error adding ad');
}

Related

Want to redirect to a page

I want to redirect to the product page. This is my controller function.
public function RegisterBusiness(Request $request){
DB::beginTransaction();
try {
if(session()->has('user_id')){
$request['mybiz_users_id']=Session()->get('user_id');
}
$this->businessTempService->insertRegisteredBusinessDetails($request);
DB::commit();
return $this->sendResponse('success', 'Registered Successfully, Thank You!', '', 200,route('web.registration.advanced-product-registration'));
} catch (Exception $ex) {
DB::rollback();
Log::error($ex);
return $this->sendResponse('error', 'Something went wrong', '', 422);
}
}
But it is not working.
It loads this page
This is not an error, its a json response , if you redirect to product page then change
return $this->sendResponse('error', 'Something went wrong', '', 422);
to
return view('product.index');
or, with name route :
return route('product');
You should use \request()->expectsJson() to find out whether you need a json response or not.
if (\request()->expectsJson()){
return $this->sendResponse('success', 'Registered Successfully, Thank You!', '', 200,route('web.registration.advanced-product-registration'));
}else{
return back();
}

How to change the response messages in Tymon JWT package laravel

I want to change response messages in the Tymon JWT package. For example, while fetching the data with Invalid token I am getting this response
"message": "Invalid token.",
"exception": "Tymon\\JWTAuth\\Exceptions\\TokenInvalidException",
I need o change this from above response to below response
"errors": "Invalid token.",
"exception": "Tymon\\JWTAuth\\Exceptions\\TokenInvalidException",
controller code
try {
$assign = AssignmentResource::collection(DB::table('assignments')->whereIn('assignments.academic_id',$ids)
->whereIn('assignments.batch',$batch)
->whereIn('assignments.course',$classid)->get());
} catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
return response()->json(['success' => false,'errors' => $e,'status' => 404] );
} catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
return response()->json(['success' => false,'errors' =>$e,'status' => 404] );
} catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
return response()->json(['success' => false,'errors' =>$e,'status' => 404] );
}
thank you in advance
You can customize the laravel Exceptions.
inside app/Exceptions/Handler.php you can customize your message.
public function render($request, Exception $exception)
{
if ($request->is('api/*') || $request->expectsJson() || $request->is('webhook/*')) {
if ($exception instanceof Tymon\JWTAuth\Exceptions\TokenInvalidExceptio) {
return [
'errors' => $exception->getMessage(),
'exception' => 'your message'
];
}
}
}
If you see Exception Handling page of Tymon JWT Auth, then it is coming soon:
One way you can achieve this is like using try..catch:
try {
// Your code here.
} catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
// return your response.
} catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
// return your response.
} catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
// return your response.
}

How to create a function to delete everything of a table? Vue.js - Laravel

I want to be able to click on a button and delete everything from a table, this is what I have now. I'm using the laravel/activitylog and thats what I'm trying to delete, I access the Model with Spatie\Activitylog\Models\Activity, but it's not a local model. I'm trying to delete everything from the table that has been created from this package.
the delete function on my vue.js, the baseUrl: 'logs'
deleteAll(){
this.$confirm('¿Desea borrar todos los registros del logs?', 'Advertencia', {
confirmButtonText: 'Si',
cancelButtonText: 'Cancelar',
cancelButtonClass: 'el-button--info',
confirmButtonClass: 'el-button--warning',
type: 'warning'
}).then(() => {
this.$inertia.delete(this.baseUrl + '/delete')
.then(
() => {
this.$message({
type: 'success',
message: 'Eliminado correctamente.'
});
this.tableData = this.tableData.filter((item) => {
return item.id !== row.id
});
},
(res) => {
this.$message.error(parseError(res)[0]);
}
)
})
},
The route
Route::delete('logs/delete', 'ActivityLogController#delete');
The delete on the controller
public function delete()
{
DB::beginTransaction();
Activity::where('log_name', 'like', '%%')->delete();
DB::commit();
return back();
}
What happens is I click it a small window with a 404 opens up inside the main window and it shows the success message but nothing gets deleted.
What am I doing wrong?
Try this in your controller:
public function delete()
{
DB::beginTransaction();
try {
Activity::truncate();
DB::commit();
} catch(\Exception $ex) {
Log::error("[ActivityDelete] An error occurred: " . $ex->getMessage());
DB::rollback();
return response()->json($ex->getMessage(), $ex->getStatusCode());
}
return response()->json(['message' => 'Success'], 200);
}
I believe the 404 is from using back(), which is not a valid response for axios. And on top of that it is not deleting anyways, so there may be a problem with the way you are trying to delete as well.
If this fails - check your storage logs and report here please.
You can change your query. For example:
Activity::where('id', '>', 0)->delete();

Laravel API, how to properly handle errors

Anyone know what is the best way to handle errors in Laravel, there is any rules or something to follow ?
Currently i'm doing this :
public function store(Request $request)
{
$plate = Plate::create($request->all());
if ($plate) {
return $this->response($this->plateTransformer->transform($plate));
} else {
// Error handling ?
// Error 400 bad request
$this->setStatusCode(400);
return $this->responseWithError("Store failed.");
}
}
And the setStatusCode and responseWithError come from the father of my controller :
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
return $this;
}
public function responseWithError ($message )
{
return $this->response([
'error' => [
'message' => $message,
'status_code' => $this->getStatusCode()
]
]);
}
But is this a good way to handle the API errors, i see some different way to handle errors on the web, what is the best ?
Thanks.
Try this, i have used it in my project (app/Exceptions/Handler.php)
public function render($request, Exception $exception)
{
if ($request->wantsJson()) { //add Accept: application/json in request
return $this->handleApiException($request, $exception);
} else {
$retval = parent::render($request, $exception);
}
return $retval;
}
Now Handle Api exception
private function handleApiException($request, Exception $exception)
{
$exception = $this->prepareException($exception);
if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
$exception = $exception->getResponse();
}
if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
$exception = $this->unauthenticated($request, $exception);
}
if ($exception instanceof \Illuminate\Validation\ValidationException) {
$exception = $this->convertValidationExceptionToResponse($exception, $request);
}
return $this->customApiResponse($exception);
}
After that custom Api handler response
private function customApiResponse($exception)
{
if (method_exists($exception, 'getStatusCode')) {
$statusCode = $exception->getStatusCode();
} else {
$statusCode = 500;
}
$response = [];
switch ($statusCode) {
case 401:
$response['message'] = 'Unauthorized';
break;
case 403:
$response['message'] = 'Forbidden';
break;
case 404:
$response['message'] = 'Not Found';
break;
case 405:
$response['message'] = 'Method Not Allowed';
break;
case 422:
$response['message'] = $exception->original['message'];
$response['errors'] = $exception->original['errors'];
break;
default:
$response['message'] = ($statusCode == 500) ? 'Whoops, looks like something went wrong' : $exception->getMessage();
break;
}
if (config('app.debug')) {
$response['trace'] = $exception->getTrace();
$response['code'] = $exception->getCode();
}
$response['status'] = $statusCode;
return response()->json($response, $statusCode);
}
Always add Accept: application/json in your api or json request.
Laravel is already able to manage json responses by default.
Withouth customizing the render method in app\Handler.php you can simply throw a Symfony\Component\HttpKernel\Exception\HttpException, the default handler will recognize if the request header contains Accept: application/json and will print a json error message accordingly.
If debug mode is enabled it will output the stacktrace in json format too.
Here is a quick example:
<?php
...
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiController
{
public function myAction(Request $request)
{
try {
// My code...
} catch (\Exception $e) {
throw new HttpException(500, $e->getMessage());
}
return $myObject;
}
}
Here is laravel response with debug off
{
"message": "My custom error"
}
And here is the response with debug on
{
"message": "My custom error",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\HttpException",
"file": "D:\\www\\myproject\\app\\Http\\Controllers\\ApiController.php",
"line": 24,
"trace": [
{
"file": "D:\\www\\myproject\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php",
"line": 48,
"function": "myAction",
"class": "App\\Http\\Controllers\\ApiController",
"type": "->"
},
{
"file": "D:\\www\\myproject\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
"line": 212,
"function": "dispatch",
"class": "Illuminate\\Routing\\ControllerDispatcher",
"type": "->"
},
...
]
}
Using HttpException the call will return the http status code of your choice (in this case internal server error 500)
In my opinion I'd keep it simple.
Return a response with the HTTP error code and a custom message.
return response()->json(['error' => 'You need to add a card first'], 500);
Or if you want to throw a caught error you could do :
try {
// some code
} catch (Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
You can even use this for sending successful responses:
return response()->json(['activeSubscription' => $this->getActiveSubscription()], 200);
This way no matter which service consumes your API it can expect to receive the same responses for the same requests.
You can also see how flexible you can make it by passing in the HTTP status code.
If you are using Laravel 8+, you can do it simply by adding these lines in Exception/Handler.php on register() method
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
For me, the best way is to use specific Exception for API response.
If you use Laravel version > 5.5, you can create your own exception with report() and render() methods. Use command:
php artisan make:exception AjaxResponseException
It will create AjaxResponseException.php at: app/Exceptions/
After that fill it with your logic. For example:
/**
* Report the exception.
*
* #return void
*/
public function report()
{
\Debugbar::log($this->message);
}
/**
* Render the exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #return JsonResponse|Response
*/
public function render($request)
{
return response()->json(['error' => $this->message], $this->code);
}
Now, you can use it in your ...Controller with try/catch functionality.
For example in your way:
public function store(Request $request)
{
try{
$plate = Plate::create($request->all());
if ($plate) {
return $this->response($this->plateTransformer->transform($plate));
}
throw new AjaxResponseException("Plate wasn't created!", 404);
}catch (AjaxResponseException $e) {
throw new AjaxResponseException($e->getMessage(), $e->getCode());
}
}
That's enough to make your code more easier for reading, pretty and useful.
Best regards!
For Laravel 8+ in file App\Exceptions\Hander.php inside method register() paste this code:
$this->renderable(function (Throwable $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => $e->getMessage(),
'code' => $e->getCode(),
], 404);
}
});
I think it would be better to modify existing behaviour implemented in app/Exceptions/Handler.php than overriding it.
You can modify JSONResponse returned by parent::render($request, $exception); and add/remove data.
Example implementation:
app/Exceptions/Handler.php
use Illuminate\Support\Arr;
// ... existing code
public function render($request, Exception $exception)
{
if ($request->is('api/*')) {
$jsonResponse = parent::render($request, $exception);
return $this->processApiException($jsonResponse);
}
return parent::render($request, $exception);
}
protected function processApiException($originalResponse)
{
if($originalResponse instanceof JsonResponse){
$data = $originalResponse->getData(true);
$data['status'] = $originalResponse->getStatusCode();
$data['errors'] = [Arr::get($data, 'exception', 'Something went wrong!')];
$data['message'] = Arr::get($data, 'message', '');
$originalResponse->setData($data);
}
return $originalResponse;
}
Well, all answers are ok right now, but also they are using old ways.
After Laravel 8, you can simply change your response in register() method by introducing your exception class as renderable:
<?php
namespace Your\Namespace;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Record not found.'
], 404);
}
});
}
}
Using some code from #RKJ best answer I have handled the errors in this way:
Open "Illuminate\Foundation\Exceptions\Handler" class and search for a method named "convertExceptionToArray". This method converts the HTTP exception into an array to be shown as a response. In this method, I have just tweaked a small piece of code that will not affect loose coupling.
So replace convertExceptionToArray method with this one
protected function convertExceptionToArray(Exception $e, $response=false)
{
return config('app.debug') ? [
'message' => $e->getMessage(),
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->map(function ($trace) {
return Arr::except($trace, ['args']);
})->all(),
] : [
'message' => $this->isHttpException($e) ? ($response ? $response['message']: $e->getMessage()) : 'Server Error',
];
}
Now navigate to the App\Exceptions\Handler class and paste the below code just above the render method:
public function convertExceptionToArray(Exception $e, $response=false){
if(!config('app.debug')){
$statusCode=$e->getStatusCode();
switch ($statusCode) {
case 401:
$response['message'] = 'Unauthorized';
break;
case 403:
$response['message'] = 'Forbidden';
break;
case 404:
$response['message'] = 'Resource Not Found';
break;
case 405:
$response['message'] = 'Method Not Allowed';
break;
case 422:
$response['message'] = 'Request unable to be processed';
break;
default:
$response['message'] = ($statusCode == 500) ? 'Whoops, looks like something went wrong' : $e->getMessage();
break;
}
}
return parent::convertExceptionToArray($e,$response);
}
Basically, we overrided convertExceptionToArray method, prepared the response message, and called the parent method by passing the response as an argument.
Note: This solution will not work for Authentication/Validation errors but most of the time these both errors are well managed by Laravel with proper human-readable response messages.
In your handler.php This should work for handling 404 Exception.
public function render($request, Throwable $exception ){
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Data not found'
], 404);
}
return parent::render($request, $exception);
}
You don't have to do anything special. Illuminate\Foundation\Exceptions\Handler handles everything for you. When you pass Accept: Application/json header it will return json error response. if debug mode is on you will get exception class, line number, file, trace if debug is off you will get the error message. You can override convertExceptionToArray. Look at the default implementation.
return config('app.debug') ? [
'message' => $e->getMessage(),
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->map(function ($trace) {
return Arr::except($trace, ['args']);
})->all(),
] : [
'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
];
As #shahib-khan said,
this happens in debug mode and is handled by Laravel in production mode.
you can see base method code in
\Illuminate\Foundation\Exceptions\Handler::convertExceptionToArray
protected function convertExceptionToArray(Throwable $e)
{
return config('app.debug') ? [
'message' => $e->getMessage(),
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->map(fn ($trace) => Arr::except($trace, ['args']))->all(),
] : [
'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
];
}
Therefore, I overrode the the convertExceptionToArray function in app/Exceptions/Handler
of course, still in debug mode, if the exception is thrown, you can track it in the Laravel.log.
protected function convertExceptionToArray(Throwable $e)
{
return [
'message' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
];
}
Add header to your API endpoint. which works for me. it will handle the error request properly.
Accept: application/json

Laravel/Vue refreshing JWT token - The token has been blacklisted exception

I am using tymon jwt auth package in laravel for token authentication and I am trying to refresh a JWT token if it is expired, I have set up a middleware AuthenticateToken, that looks like this:
class AuthenticateToken
{
public function handle($request, Closure $next)
{
try
{
if (! $user = JWTAuth::parseToken()->authenticate() )
{
return response()->json([
'code' => 401,
'response' => null
]);
}
}
catch (TokenExpiredException $e)
{
// If the token is expired, then it will be refreshed and added to the headers
try
{
$refreshed = JWTAuth::refresh(JWTAuth::getToken());
$user = JWTAuth::setToken($refreshed)->toUser();
header('Authorization: Bearer ' . $refreshed);
}
catch (JWTException $e)
{
return response()->json([
'code' => 403,
'response' => null
]);
}
}
catch (JWTException $e)
{
return response()->json([
'code' => 401,
'response' => null
]);
}
// Login the user instance for global usage
Auth::login($user, false);
return $next($request);
}
}
And I am using that middleware on my routes:
Route::group(['prefix' => 'intranet', 'middleware' => ['token']], function () {
Route::get('intranet-post', 'Api\IntranetController#index');
});
And in Vue I have set up the axios and refreshing of the token like this:
// Apply refresh(ing) token
BACKEND.defaults.transformResponse.push((data, headers) => {
if (headers.authorization && store('token', headers.authorization)) {
BACKEND.defaults.headers.common.authorization = headers.authorization;
}
return data;
});
BACKEND.defaults.transformRequest.push((data, headers) => {
headers.authorization = `Bearer ${load('token')}`;
});
Vue.prototype.$http = axios;
Vue.prototype.$backend = BACKEND;
function store(key, value) {
try {
let oldLength = localStorage.length;
localStorage.setItem(key, value);
return !(localStorage.length > oldLength); // Returns true on write error
}
catch (err) {
return true;
}
}
function load(key) {
try {
return localStorage.getItem(key);
}
catch (err) {
return null;
}
}
But, on expiration of the token I still get 403 response. If I do dd($e) in middleware here:
catch (TokenExpiredException $e)
{
// If the token is expired, then it will be refreshed and added to the headers
try
{
$refreshed = JWTAuth::refresh(JWTAuth::getToken());
$user = JWTAuth::setToken($refreshed)->toUser();
header('Authorization: Bearer ' . $refreshed);
}
catch (JWTException $e)
{
dd($e);
return response()->json([
'code' => 103,
'response' => null
]);
}
}
I get:
The token has been blacklisted exception
How can I fix this?
Try my middleware:
<?php
namespace App\Http\Middleware;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Tymon\JWTAuth\Exceptions\JWTException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
class RefreshToken extends BaseMiddleware {
public function handle($request, \Closure $next) {
$this->checkForToken($request); // Check presence of a token.
try {
if (!$this->auth->parseToken()->authenticate()) { // Check user not found. Check token has expired.
throw new UnauthorizedHttpException('jwt-auth', 'User not found');
}
$payload = $this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray();
return $next($request); // Token is valid. User logged. Response without any token.
} catch (TokenExpiredException $t) { // Token expired. User not logged.
$payload = $this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray();
$key = 'block_refresh_token_for_user_' . $payload['sub'];
$cachedBefore = (int) Cache::has($key);
if ($cachedBefore) { // If a token alredy was refreshed and sent to the client in the last JWT_BLACKLIST_GRACE_PERIOD seconds.
\Auth::onceUsingId($payload['sub']); // Log the user using id.
return $next($request); // Token expired. Response without any token because in grace period.
}
try {
$newtoken = $this->auth->refresh(); // Get new token.
$gracePeriod = $this->auth->manager()->getBlacklist()->getGracePeriod();
$expiresAt = Carbon::now()->addSeconds($gracePeriod);
Cache::put($key, $newtoken, $expiresAt);
} catch (JWTException $e) {
throw new UnauthorizedHttpException('jwt-auth', $e->getMessage(), $e, $e->getCode());
}
}
$response = $next($request); // Token refreshed and continue.
return $this->setAuthenticationHeader($response, $newtoken); // Response with new token on header Authorization.
}
}
For more details see this post.
You can edit your config/jwt.php about line 224.
<?php
.......
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', false),

Resources