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
Related
I am trying to use Service-Repository pattern for the first time in my Laravel app and I have to handle a situation: a record can´t be added to a table (for example because trying to add same unique email....).
I have CustomAuthCOntroller with customRegistration method:
//Validatting request
$request->validated();
//running logic behind this in service
$is_Registered = $this->loginService->register_user(
$request->get('email'),
$request->get('password'),
$request->get('name'),
$request->get('surname')
);
// If everything goes right shol login page
if ($is_Registered) {
return redirect("/login")->withSuccess('Registrácia prebehla úspešne');
} else {
// Else HERE I HAVE TO SHOW what error happened
return redirect("/register")->withError('Registrácia prebehla úspešne');
}
My LoginService->register_user:
public function register_user($email, $password, $firstname, $lastname, $role = 'student') {
$user_attributes = [
'email' => $email,
'password' => $password,
'name' => $firstname,
'surname' => $lastname,
];
$this->userRepositor->create_new_user($user_attributes);
}
And my userRepository->create_new_user:
public function create_new_user(Array $attributes) : bool
{
DB::beginTransaction();
try {
User::create($attributes);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
}
}
Is this right way how to achieve my goal? How can I show the error in my view if a exception is thrown?
You should not have any problems as you should be validating your input, so if the email/username or whatever that should be unique does already exist, then the validation should fail and return automatically with the errors (no try/catch).
If, you still want to catch any other possible error, let's say the DB timed out because of a connection problem or whatever (something not related to data), then you could either have a general try/catch (as I will show you) or do not catch it at all and let it go to the user, but this last approach depends a lot in your frontend:
If you have an API, then JS should handle this and show the error correctly.
If this is not an API (no AJAX), then the error could be shown to the user as a 500 error (0% descriptive for the user and would be a blank page with a 500 on it).
So, if you want the try/catch approach, you could do this:
//Validatting request
$request->validated();
try {
//running logic behind this in service
$this->loginService->register_user(
$request->get('email'),
$request->get('password'),
$request->get('name'),
$request->get('surname')
);
// If everything goes right shol login page
return redirect("/login")->withSuccess('Registrácia prebehla úspešne');
} catch (Throwable $exception) {
// HERE I HAVE TO SHOW what error happened
// You can even add the exception error with $exception->getMessage()
return redirect("/register")->withError('Registrácia prebehla úspešne');
}
And then, change your create_new_user to either of these:
public function create_new_user(Array $attributes) : bool
{
DB::beginTransaction();
try {
User::create($attributes);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
Or use DB::transaction(). It will automatically rollback and throw the exception. See the source code here and here.
public function create_new_user(Array $attributes) : bool
{
DB::transaction(function () use ($attributes) {
User::create($attributes);
});
}
One personal recommendatation, do not declare methods as kebab_case like create_new_user, follow the PSR standard and Laravel standard, it should be camelCase: createNewUser.
I have a controller entry point where I execute another method from my ProductService inside a try catch block, I pretend to catch all exceptions that may occur inside $this->productService->create() method, except for Validation errors, if it's a validation error $e->getMessage() won't do, since I'll get generic response "Given data was invalid" instead of full custom messages. After reading some, I decided to use render method in laravel Handler class, I added this:
//In order to react to validation exceptions I added some logic to render method, but it won't actually work, I'm still getting normal exception message returned.
public function render($request, Exception $exception)
{
if ($request->ajax() && $exception instanceof ValidationException) {
return response()->json([
'message' => $e->errors(),
],422);
}
return parent::render($request, $exception);
}
However I'm still getting the default message, this means that my catch block is catching normal exception instead of my custom render method...
In my controller, try catch block looks like this:
try
{
$this->productService->create($request);
return response()->json([
'product' => $product,
], 200);
}
//I want to catch all exceptions except Validation fails here, and return simple error message to view as
json
catch (\Exception $e)
{
return response()->json([
'message' => $e->getMessage(),
], $e->getStatus() );
}
Also, in ValidationException, I cannot use $e->getCode, $e->getStatus(), it will always return 0 or sometimes 1, afaik it should be 422, that's why in my render method I'm manually returning 422. In my try catch block with normal Exceptions $e->getCode() works correctly, why is that?
In your render function, you are referencing an error instance that isn't defined, you have define Exception $exception but you are referencing $e->errors();
You code should be:
public function render($request, Exception $exception)
{
if ($request->ajax() && $exception instanceof ValidationException) {
return response()->json([
'message' => $exception->errors(),
],422);
}
return parent::render($request, $exception);
}
Change $e->errors(); to $exception->errors();
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.
With regards to architecture, which of the two is a good practice when throwing exceptions from model to controller?
Structure A:
UserController.php
public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
$isError = false;
$message = 'Success';
try {
$message = $userModel->updateUserInfo($request->only(['username', 'password']));
} catch (SomeCustomException $e) {
$isError = true;
$message = $e->getMessage();
}
return json_encode([
'isError' => $isError,
'message' => $message
]);
}
UserModel.php
public function updateUserInfo($request)
{
$isError = false;
$message = 'Success';
$username = $request['username'];
$password = $request['password'];
try {
$this->connect()->beginTransaction();
$this->connect()->table('users')->where('username', $username)->update(['password' => $password]);
$this->connect()->commit();
} catch (\Exception $e) {
$this->connect()->rollback();
$isError = true;
$message = $e->getMessage();
}
return [
'isError' => $isError,
'message' => $message
];
}
Structure B:
UserController.php
public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
$isError = false;
$message = 'Success';
try {
$userModel->updateUserInfo($request->only(['username', 'password']));
} catch (SomeCustomException $e) {
$isError = true;
$message = $e->getMessage();
} catch (QueryException $e) {
$isError = true;
$message = $e->getMessage();
}
return json_encode([
'isError' => $isError,
'message' => $message
]);
}
UserModel.php
public function updateUserInfo($request)
{
$username = $request['username'];
$password = $request['password'];
try {
$this->connect()->beginTransaction();
$this->connect()->table('users')->where('username', $username)->update(['password' => $password]);
$this->connect()->commit();
} catch (\Exception $e) {
$this->connect()->rollback();
throw new QueryException();
}
}
In Structure A, the model catches any exception, rollback the transaction and return if it has an error or none to the controller. The controller then just return whatever is returned from the model.
While in Structure B the model catches any exception, rollback the transaction then throw a QueryException if an exception occurred. The controller then catches the thrown QueryException from the model then the return if it has an error or none.
The reason why Structure B still have a catch is that the model should be the one to do the rollback. If I were to remove the try-catch on the model here and the controller to directly catch an exception, then the rollback will be handled on the controller which I think kind of clutters the functionality of the controller.
Let me know your thoughts.
Thanks!
Why I think the approach from B is better:
Your Model should only include the logical part: This includes the communication with the database (transaction and rollback), not the formatting for the error message you want to print to the user.
Keep your model clean: It's the most important part of the MVC-structure. If you mess it up it will be very difficult to find any errors.
Outsourcing the error-handling: if you put it in the controller you have the choice to handle it there (maybe you want some special formatted output for this method or you need some other functions to call) or you handle it in the App\Exceptions\Handler. In this case you can render this error message here and don't have to do it in the controller.
So if you don't need any special function calls and want to use the full power of Laravel I would suggest you Structure C
UserController.php
public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
$userModel->updateUserInfo($request->only(['username', 'password']));
return response()->json(['message' => 'updated user.']);
}
UserModel.php
public function updateUserInfo($request)
{
$username = $request['username'];
$password = $request['password'];
try {
$this->connect()->beginTransaction();
$this->connect()->table('users')->where('username', $username)->update(['password' => $password]);
$this->connect()->commit();
} catch (\Exception $e) {
$this->connect()->rollback();
throw new QueryException();
}
}
App\Exceptions\Handler
public function render($request, Exception $exception)
{
//catch everything what you want
if ($exception instanceof CustomException) {
return response()->json([
'message' => $exception->getMessage()
], 422);
}
return parent::render($request, $exception);
}
You have a clean separation of the Database stuff (Model), the presentation stuff (Controller) and the error handling (Handler). Structure C allows you to reuse the error-handling in other functions where you have the same situation in another controller function.
This is my opinion but I am open to discuss about any scenario where you think this approach isn't the best solution.
First of all, for your example, you don't even need to use Transaction. You are performing just one query. so why do you need to rollback? Which query you want to rollback? A transaction should be used when you need a set of changes to be processed completely to consider the operation complete and valid. If the first one is successful, but any of the following has any error, you can rollback everything as if nothing was ever done.
Secondly, Lets come to the point good practice or best practice. Laravel suggest Thin controller and Thick model. So all of your business logic should be in model or even better in a repository. Controller will be act as a broker. It will collect data from repository or model and pass it to view.
Alternately, laravel provides some nice and convenient way to organize your codes. You can use Event and Observers for concurrent operation in your model.
Best practice is varies based on the knowledge and experience of the users. So who knows, the best answer for your question is yet to come.
I'd rather keep the controllers and any other part of the system that interacts with the models, as agnostic as possible about the inner workings of the model. So for instance I'd try to avoid being aware of QueryExceptions outside of the model and instead treat it as a plain PHP object whenever possible.
Also I'd avoid the custom JSON response structure and use HTTP statuses. If it makes sense, maybe the route for updating the user info returns the updated resource, or maybe a 200 OK is enough.
// UserModel.php
public function updateUserInfo($request)
{
$username = $request['username'];
$password = $request['password'];
try {
$this->connect()->beginTransaction();
$this->connect()->table('users')->where('username', $username)->update(['password' => $password]);
$this->connect()->commit();
return $this->connect()->table('users')->where('username', $username)->first();
// or just return true;
} catch (\Exception $e) {
$this->connect()->rollback();
return false;
}
}
// UserController.php
public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
$updated = $userModel->updateUserInfo($request->only(['username', 'password']));
if ($updated) {
return response($updated);
// HTTP 200 response. Returns JSON of updated user.
// Alternatively,
// return response('');
// (200 OK response, no content)
} else {
return response('optional message', 422);
// 422 or any other status code that makes more sense in the situation.
}
(Completely off-topic, I guess this is an example, but just in case, a reminder not to store plain text passwords.)
i don't understand, why you not watched Jeffry lesson, but for updating user you don't need try/catch section.
you controller method:
public function update(UpdateUserRequest $request, User $user) : JsonResponse
{
return response()->json($user->update($request->all()))
}
you request rules method:
public function rules(): array
{
return [
'username' => 'required|string',
'password' => 'required|min:6|confirmed',
];
}
And you Exception Handler render method:
public function render($request, Exception $exception)
{
if ($request->ajax() || $request->wantsJson()) {
$exception = $this->prepareException($exception);
if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
return $exception->getResponse();
} elseif ($exception instanceof \Illuminate\Auth\AuthenticationException) {
return $this->unauthenticated($request, $exception);
} elseif ($exception instanceof \Illuminate\Validation\ValidationException) {
return $this->convertValidationExceptionToResponse($exception, $request);
}
// we prepare custom response for other situation such as modelnotfound
$response = [];
$response['error'] = $exception->getMessage();
if (config('app.debug')) {
$response['trace'] = $exception->getTrace();
$response['code'] = $exception->getCode();
}
// we look for assigned status code if there isn't we assign 500
$statusCode = method_exists($exception, 'getStatusCode')
? $exception->getStatusCode()
: 500;
return response()->json($response, $statusCode);
}
return parent::render($request, $exception);
}
Now, if you have Exception, Laravel give you in Json with status code != 200, else give success result!
In laravel5, I have catching all error at app/Exceptions/Handler#render function and it was working fine.
code given below,
public function render($request, Exception $e) {
$error_response['error'] = array(
'code' => NULL,
'message' => NULL,
'debug' => NULL
);
if ($e instanceof HttpException && $e->getStatusCode() == 422) {
$error_response['error']['code'] = 422;
$error_response['error']['message'] = $e->getMessage();
$error_response['error']['debug'] = null;
return new JsonResponse($error_response, 422);
}
}
return parent::render($request, $e);
}
But in laravel5.1,When form validation failes,it throws error message with 422exception. but it is not catching from app/Exceptions/Handler#render but working fine with abort(422).
How can I solve this?
You can catch simply by doing
public function render($request, Exception $e) {
if($e instanceof ValidationException) {
// Your code here
}
}
When Form Request fails to validate your data it fires the failedValidation(Validator $validator) method that throws HttpResponseException with a fresh Redirect Response, but not HttpException. This exception is caught via Laravel Router in its run(Request $request) method and that fetches the response and fires it. So you don't have any chance to handle it via your Exceptions Handler.
But if you want to change this behaviour you can overwrite failedValidation method in your Abstract Request or any other Request class and throw your own exception that you will handle in the Handler.
Or you can just overwrite response(array $errors) and create you own response that will be proceed by the Router automatically.