Query2ValidationException throw mysql QueryException which relate to business logic to ValidationException.
For example:
500 Column ':attribute' cannot be null
to
422 The :attribute field is required.
composer require asmgit/query2validation-exception
app/Exception/Handler.php
use Asmgit\ValidationException;
...
public function report(Exception $exception)
{
if (Query2ValidationException::check($exception)) {
$this->dontReport[] = QueryException::class;
return (new Query2ValidationException($exception))->render();
}
...
Now you can remove most common Validation's from your code and DB will take care of your data.
After install most common exception will translate to ValidationException:
- ER_BAD_NULL_ERROR, 1048, (Column '%s' cannot be null) to The :attribute field is required.
- ER_DUP_ENTRY, 1062, (Duplicate entry '%s' for key %d) to The :attribute has already been taken.
- ER_DATA_TOO_LONG, 1406, (Data too long for column '%s' at row %ld) to The :attribute is too long.
- WARN_DATA_TRUNCATED, 1265, (Data truncated for column '%s' at row %ld) to The selected :attribute is invalid.
- ER_TRUNCATED_WRONG_VALUE_FOR_FIELD. 1366, (Incorrect %s value: '%s' for column '%s' at row %ld) to The :attribute must be an :field_type type.
You can get all builtin messages
dd(Query2ValidationException::getMessageTemplates());
Output
array:5 [
1048 => array:3 [
"orig" => "Column '(.*?)' cannot be null"
"new" => "The :attribute field is required."
"params" => array:1 [
1 => "attribute"
]
]
1062 => array:4 [
"orig" => "Duplicate entry '(.*?)' for key '(.*?)'"
"new" => "The :attribute has already been taken."
"params" => array:2 [
1 => "value"
2 => "index_name"
]
"params_post_process" => Closure(&$ex) {#736
class: "App\Exceptions\Query2ValidationException"
}
]
1406 => array:3 [
"orig" => "Data too long for column '(.*?)' at row ([0-9]+)"
"new" => "The :attribute is too long."
"params" => array:2 [
1 => "attribute"
2 => "rownum"
]
]
1265 => array:3 [
"orig" => "Data truncated for column '(.*?)' at row ([0-9]+)"
"new" => "The selected :attribute is invalid."
"params" => array:2 [
1 => "attribute"
2 => "rownum"
]
]
1366 => array:3 [
"orig" => "Incorrect (.*?) value: '(.*?)' for column '(.*?)' at row ([0-9]+)"
"new" => "The :attribute must be an :field_type type."
"params" => array:4 [
1 => "field_type"
2 => "value"
3 => "attribute"
4 => "rownum"
]
]
]
Query2ValidationException::addValidationMessage(
'Please, fill your first name.', // new message
Query2ValidationException::ER_BAD_NULL_ERROR, // error type
'first_name' // field
);
$user->save();
array_pop(Query2ValidationException::$customValidationMessages);
will return for ajax request
{
"message":"The given data was invalid.",
"errors":{
"first_name":[
"Please, fill your first name."
]
}
}
Messages will generate for all fields in unique key.
For example, you need set unique user full_name for each account_id.
Create unique constraint:
ALTER TABLE users
ADD UNIQUE INDEX users_account_id_full_name_unique (account_id,full_name)
Get validation exception:
{
"message":"The given data was invalid.",
"errors":{
"account_id":[
"The account_id,full_name has already been taken."
],
"full_name":[
"The account_id,full_name has already been taken."
]
}
}
Declare COMMENT for unique key
ALTER TABLE users
DROP INDEX users_account_id_full_name_unique
, ADD UNIQUE INDEX users_account_id_full_name_unique (account_id,full_name)
COMMENT 'User must be unique for each account. :value has already been taken.'
{
"message":"The given data was invalid.",
"errors":{
"account_id":[
"User must be unique for each account. 1-John Smith has already been taken."
],
"full_name":[
"User must be unique for each account. 1-John Smith has already been taken."
]
}
}
You can use params such :value. See all params (Output above):
dd(Query2ValidationException::getMessageTemplates());
public function store(Request $request)
{
...
Query2ValidationException::addValidationMessage(
'User already exists in this account.', // new message
Query2ValidationException::ER_DUP_ENTRY, // error type
'account_id,full_name', // all fields in composite unique key
'first_name,last_name' // new field/fields
);
$user->save();
array_pop(Query2ValidationException::$customValidationMessages);
...
public function update($id, Request $request)
{
...
Query2ValidationException::addValidationMessage(
'User already exists in this account.', // new message
Query2ValidationException::ER_DUP_ENTRY, // error type
'account_id,full_name', // all fields in composite unique key
'first_name,last_name' // new field/fields
);
$user->save();
array_pop(Query2ValidationException::$customValidationMessages);
{
"message":"The given data was invalid.",
"errors":{
"first_name":[
"User already exists in this account."
],
"last_name":[
"User already exists in this account."
]
}
}
- check error format in mysql doc site:
https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html - Intitialize builtin messages
Query2ValidationException::$messageTemplates = Query2ValidationException::getMessageTemplates();
- create new message template
Query2ValidationException::$messageTemplates[<errorNo>] = ...
- also you can change builtin messages
Example for mysql >= 8.0.16 with check constraint support:
app/Exception/Handler.php
use Asmgit\ValidationException;
...
public function report(Exception $exception)
{
// setup builtin messageTemplates
Query2ValidationException::$messageTemplates = Query2ValidationException::getMessageTemplates();
// Error number: 3819; Symbol: ER_CHECK_CONSTRAINT_VIOLATED; SQLSTATE: HY000 Message: Check constraint '%s' is violated.
Query2ValidationException::$messageTemplates[3819] = [
'orig' => "Check constraint '(.*?)\\|(.*?)\\|(.*)' is violated.",
'new' => "Check constraint is violated: :message",
'params' => [1 => 'table_name', 2 => 'attribute', 3 => 'message']
];
if (Query2ValidationException::check($exception)) {
$this->dontReport[] = QueryException::class;
return (new Query2ValidationException($exception))->render();
}
...
Create and raise any CHECK CONSTRAINT
ALTER TABLE users
ADD CONSTRAINT `users|first_name,last_name|first name must be longer than last name.` CHECK (first_name > last_name);
{
"message":"The given data was invalid.",
"errors":{
"first_name":[
"Check constraint is violated: first name must be longer than last name."
],
"last_name":[
"Check constraint is violated: first name must be longer than last name."
]
}
}
- Laravel Validation can raise all errors at one time, Query2ValidationException only one, because Mysql raise only one first error and stop executing.
- If you use Laravel Validation you need describe constraints in DB and dublicate validation logic in app layer YourController@store, YourController@update. With Query2ValidationException you can just describe constraints only in DB.
- Some exceptions you can't catch in app layer. For example unique constraints. This code work fine in oneuser application.
public function store(Request $request)
{
$validatedData = $request->validate([
'full_name' => 'required|unique:users',
]);
sleep(20);
$user->full_name = $request->input('full_name');
$user->save();
...
But if you run this code with new unique full_name twice, one session save data, second get 500 exception (with correct unique constraint DB config, without constraint, you get incorrect data in DB)