Laravel display validation error in multiple languages at the same time - php

I am working on returning validation errors in multiple languages at the same time.
I have a controller that injects a class that extends FormRequest and I am overriding 'failedValidation' and there I get the validator error messages.
public function store(SysUserStoreRequest $request)
{
// ...
}
class SystemUserStoreRequest extends ApiRequest
{
// This extends another class ApiRequest
Here I defined rules()
}
class APIRequest extends FormRequest
{
// Here I override the FailedValidation method.
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException($this->response($validator->getMessageBag()->toArray()));
}
}
The above code currently returns the error in the default language.
I can change the response to show in a different language by changing the locale in the middleware, but I am working on requirement where I need to return a new structure of validation error with each field errors in both en and fr.
I need the structure like below:
{
"detail": {
"email": {
"en-CA" : [
"The email has already been taken."
],
"fr-CA" : [
"The french text."
]
},
"first_name": {
"en-CA" : [
"The first name must be at least 5.",
"The first name must be an integer."
],
"fr-CA" : [
"The french text",
"The french text."
]
}
}
}
So i tired to override the failedValidation method and do something like below:
$validation_structure = [];
if ($validator->fails()) {
$failedRules = $validator->failed();
}
after getting the al the failed rules i can then get the string from the lang folder for each locale and get the string for the field and rule and generate the message using
$x[] = __('validation.unique', [], 'fr-CA');
$x[] = __('validation.unique', [], 'en-CA');
this will give me the string in both labguages but I do not know how to replace the :attributes, :values and various other string replacements.

You could overwrite the message bag the SystemUserStoreRequest will give back to format the messsages.
class SystemUserStoreRequest extends ApiRequest
{
public function rules()
{
return [
'email' => 'required|unique:users,email,' . $this->id,
];
}
public function messages()
{
return [
'email.required' => [
'nl' => __('validation.required', ['attribute' => __('portal.email', [],'nl')], 'nl'),
'en' => __('validation.required', ['attribute' => __('portal.email', [],'en')], 'en'),
],
'email.unique' => [
'nl' => __('validation.unique', ['attribute' => __('portal.email', [],'nl')], 'nl'),
'en' => __('validation.unique', ['attribute' => __('portal.email', [],'en')], 'en'),
]
];
}
}
Then the output would look like:
{
"message":"The given data was invalid.",
"errors":{
"email":[
{
"nl":"E-mailadres is verplicht.",
"en":"The email field is required."
}
]
}
}
Here is some more documentation about custom messages:
https://laravel.com/docs/8.x/validation#specifying-custom-messages-in-language-files

Related

Creating two seperate validation messages for the same attribute

I'm trying to create two separate validation messages for the same validation attribute.
There are two rules that use "before_or_equal" - the end_time has to be "before_or_equal" start_time and also the end_time has to be after 5:00 (time). The validation works, but I can't seem to find a way to create a working custom message for the latter.
I tried to specify the rule by including it literally with the value, but it doesn't seem to work.
This is what the custom request validation looks like for the end_time at the moment.
public function rules()
{
return [
'end_time' => ['after_or_equal:start_time', 'after_or_equal:5:00'],
];
}
public function messages()
{
return [
'end_time.after_or_equal' => 'Message 1',
'end_time.after_or_equal:5:00' => 'Message 2',
];
}
You can use :date for your custom error messages.
Example:
public function rules()
{
return [
'end_time' => ['after_or_equal:start_time', 'after_or_equal:5:00'],
];
}
public function messages()
{
return [
'end_time.after_or_equal' => 'the :attribute time must be after :date',
];
}
The replaced value is actual value of first input of the validator
i don't know if i understand your question correctly, but are you looking for something like this ?
public function rules()
{
return $this->messages("end_time", [
"after_or_equal",
"after_or_equal:5:00",
]);
}
public function messages(string $Key, array $CustomAttributes)
{
$Exceptions = [
"end_time" => [
"after_or_equal" => "Message 1",
"after_or_equal:5:00" => "Message 2"
]
];
$Exception = [
$Key => []
];
foreach ($CustomAttributes as $Attribute) {
array_push($Exception[$Key], $Exceptions[$Key][$Attribute]);
}
return $Exception;
}

Validation-messages inside a FormRequest for an integer-array are not accepted

I'm using FormRequest to validate, also overriding messages function to set custom messages.
Somehow, I can't get integer-array validation to work. This is where I'm at:
This is what my request should look like:
// ACCEPTED:
{
"name": "my name",
"items": [20, 7, 1, 2]
}
// REJECTED
{
"name": "Meine neue Kampagne",
"items": ["ANY STRING HERE", 1, 2]
}
So I want my items to only be an array of integers. These are my rules, which are fully working:
return [
'name' => 'required|max:191',
'items' => 'array',
'items.*' => 'int|exists:jobpostings,id'
];
So far, I tried the following (all found in online tutorials):
return [
'items.*.int' => "item is not int",
];
return [
'items.*[
'int' => "item is not int"
]
];
They all just gave the common "The items.0 must be an integer." message.
Can anyone help me maybe?
Here is the complete FormRequest:
<?php
namespace App\Http\Requests\Campaign;
use Illuminate\Foundation\Http\FormRequest;
class CreateCampaignRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|max:191',
'items' => 'array',
'items.*' => 'int|exists:items,id'
];
}
public function messages()
{
return [
'name.required' => __('campaign.createCampaign.name_required'),
'name.max' => __('campaign.createCampaign.name_max'),
'items.array' => __('campaign.createCampaign.items_array'),
'items.*.int' => __('campaign.createCampaign.items_*_int'),
'items.*.exists' => __('campaign.createCampaign.items_*_exists'),
];
}
}

Yii2 prevent TimestampBehavior

I am creating a custom Identity interface without created_at property. I got an error :
"name": "Unknown Property",
"message": "Setting unknown property: api\\common\\models\\User::created_at",
I tried to comment the TimestampBehavior, but I got the following error:
"name": "PHP Warning",
"message": "Invalid argument supplied for foreach()",
I want to know where is the problem.
Model class:
class User extends ActiveRecord implements IdentityInterface
{
public static function tableName()
{
return '{{%user}}';
}
public function behaviors()
{
// return [
// TimestampBehavior::className(),
// ];
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['purpose'], 'required'],
[['status'], 'integer'],
];
}
}
for the rest controller the action is
public function actionLogin(){
.
.
.
$api_user = new User();
$api_user->purpose="app";
$api_user->status=User::STATUS_ACTIVE;
if($api_user->save()){
$success = true;
}
}
This will automatically resolve the issue. BlameableBehavior and TimestampBehavior
// Include these on the start
use yii\behaviors\BlameableBehavior;
use yii\behaviors\TimestampBehavior;
use Carbon\Carbon;
// Paste this function inside the class.
/**
* #return array
*/
public function behaviors()
{
return [
'blameable' => [
'class' => BlameableBehavior::className(),
'createdByAttribute' => 'created_by',
'updatedByAttribute' => 'updated_by',
],
'timestamp' => [
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'created_at',
'updatedAtAttribute' => 'updated_at',
'value' => Carbon::now(),
],
];
}
NOTE: If you are not using updated_at or updated_by then remove it
form the above code
change your Behavior in your model to:
public function behaviors()
{
return [
'timestamp' => [
'class' => 'yii\behaviors\TimestampBehavior',
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['updated_at'],
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
],
'value' => new Expression('NOW()'),
],
];
}
if you haven't updated_at also delete it from attributes.
You were getting following warning because you've completely removed the return in the behaviors() method.
"name": "PHP Warning",
"message": "Invalid argument supplied for foreach()",
The behaviors method must return an array. If you don't want to use any behavior your behaviors() method should return empty array like this:
public function behaviors()
{
return [];
}
This is also default implementation of behaviors() method in yii\base\Component so if you don't need to use any behavior you can simply remove the behaviors() method from your model.
Attaching TimestampBehavior to your model when you are not using it means that you add unnecessary overhead.
Example: Rename and prevent time recording or remove properties. Also change the value
Rename or delete properties or change value.
public function behaviors()
{
return [
[
'class' => \yii\behaviors\TimestampBehavior::className(),
'createdAtAttribute' => 'created_at',
// 'createdAtAttribute' => 'c_time', //Change the name of the field
'updatedAtAttribute' => false, //false if you do not want to record the creation time.
// 'value' => new Expression('NOW()'), // Change the value
],
];
}
Or
'class' => \yii\behaviors\TimestampBehavior::className(),
'attributes' => [
\yii\db\ActiveRecord::EVENT_BEFORE_INSERT => ['created_at'],
// \yii\db\ActiveRecord::EVENT_BEFORE_UPDATE => [],
],
$createdAtAttribute: The attribute that will receive timestamp value Set this property to false if you do not want to record the creation time.
$attributes: List of attributes that are to be automatically filled with the value specified via $value. The array keys are the ActiveRecord events upon which the attributes are to be updated, and the array values are the corresponding attribute(s) to be updated. You can use a string to represent a single attribute, or an array to represent a list of attributes. For example,
[
ActiveRecord::EVENT_BEFORE_INSERT => ['attribute1', 'attribute2'],
ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
]

Laravel / nested validation

I am building a component-driven API for a basic page-builder system and have hit a stumbling block when it comes to validation.
First, I want to explain the use-case.
if we have a component (for example in Vue) in /components/ProfileCard.vue
<script>
export default {
props: {
name: String,
age: Number,
avatar: String
}
}
</script>
I am creating a component in the backend components.php config:
<?php
return [
'profile' => [
'component' => 'ProfileCard',
'rules' => [
'name' => [
'required',
],
'age' => [
'required',
'number',
],
'avatar' => [
'required',
]
],
],
];
Which checks and validates every time a profile card component is submitted.
Creating a custom validation rule for Component, I am able to say "the ProfileCard component is not valid" but I am not able to merge / nest the validation rules:
Component.php
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Validator;
class Component implements Rule
{
protected $validator = null;
public function passes($attribute, $value)
{
$components = config('components');
$component = $value['component'];
if (isset($components[$component])) {
return false;
}
$c = $components[$component];
$this->validator = Validator::make($value['data'], $c['rules'], $c['messages'] ?? '');
return $this->validator->passes();
}
public function message()
{
if (is_null($this->validator)) {
return 'The component does not exist';
}
return $this->validator->errors();
}
}
Has anybody got any experience doing anything like this or can anybody point me in the right direction towards a solution?
I am ideally looking for a solution which is applicable while using Laravel's FormRequest validation, like so:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Unique;
use App\Rules\Component;
class CreateUserRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'email' => [
'required',
'email',
new Unique('users', 'email'),
],
'profile' => [
'required',
new Component(),
]
];
}
}
The data would come in like so:
{
"email": "test#example.com",
"profile": {
"component": "ProfileCard",
"data": {
"name": "Test",
"age": 49,
"avatar": "https://example.com/avatar.png"
}
}
}
I have updated the question with the progress I have made myself, you can return a MessageBag in the messages method on the rule, however, this creates a slight problem, the response comes back as follows:
"message": "The given data was invalid.",
"errors": {
"profile": [
{
"name": [
"The name field is required."
],
"age": [
"The age field is required."
],
"avatar": [
"The avatar field is required."
],
},
":message"
]
}
Clearly this is an improvement but it's still not as usable, we don't have a ':message' and the validation errors are nested in an object in the "profile" array.
Your approach seems like you are over complicating a simple problem. I would never do a validation in a validation rule. Instead do rules that is dependent on the component and adjust it in the form request accordingly. You can easily do nested rules like so.
[
'profile.name' => 'string',
]
Do the rest of the logic in the form request. The strategy is to have rules based on the request input and your config file, based on what you already tried.
public function rules()
{
// i do not know how you determine this key
$componentKey = 'profile';
$rules = [
...,
$componentKey => [
'required',
]
];
$inputComponent= $this->input('profile')['component'];
$components = config('components');
// based on your data this seems wrong, but basically fetch the correct config entry
$component = $components[$inputComponent];
foreach ($component['rules'] as $key => $value) {
$rules[$componentKey . '.' . $key] => $value;
}
return $rules;
}
There is some parts of your code where i can't figure out what your data means, i do not know how you get the component key profile and your code based on the config and the component field seems wrong and should instead do a loop with a where condition. I think that this can get you in the right direction, this solution will solve your message problem and be way simpler.

Customizing The Flashed Error Format - Laravel 5.3

I want to customize the format of flashed error message, that is recieved after i do something like $this->validator($request->all())->validate();
For an ajax request, it responds with:
{
"name": [
"The name field is required."
],
"email": [
"The email field is required."
],
}
But i want it to look like
{
"status": "fail",
"errors": {
"name": [
"The name field is required."
],
"email": [
"The email field is required."
],
}
}
I read the documentation under Customizing The Flashed Error Format and added formatValidationErrors method in my Controller class, but it's not making any difference.
public function formatValidationErrors(Validator $validator)
{
return ['status' => 'fail', 'errors' => $validator->errors()->getMessages()];
}
It's not changing even if i change formatValidationErrors method in the original Illuminate\Foundation\Validation\ValidatesRequests trait
I'm forced have to build this format in all request calls and so there's code-duplication. It would be nice if i could just call $this->validator($request->all())->validate() and it automatically formats as per my requirement.
The way to change it is to create a class that extends Validator and override the addError method. Here's a sample code:
<?php
namespace App\Validators;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\Validator;
class RestValidator extends Validator {
/**
* Add an error message to the validator's collection of messages.
*
* #param string $attribute
* #param string $rule
* #param array $parameters
* #return void
*/
protected function addError($attribute, $rule, $parameters)
{
$message = $this->getMessage($attribute, $rule);
$message = $this->doReplacements($message, $attribute, $rule, $parameters);
$customMessage = new MessageBag();
$customMessage->merge(['code' => strtolower($rule.'_rule_error')]);
$customMessage->merge(['message' => $message]);
$this->messages->add($attribute, $customMessage);
}
}
Now you can structure the validation in your controllers like so:
$rules = [
// Your rules here
];
$attributes = [
// The attributes you're checking here
];
$validator = Validator::make($attributes, $rules);
if ($validator->fails()) {
$errorMessage = [
'status' => 'fail',
'errors' => $validator->errors()
];
return $errorMessage;
}
// The rest of your code goes here
Try this in your model,
public function response(array $errors)
{
if (($this->ajax() && !$this->pjax()) || $this->wantsJson()) {
$errors = array('status' => 'fail', 'errors' => $errors);
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
It will result as you expected,
{
"status": "fail",
"error": {
"name": [
"The name field is required."
],
"email": [
"The email field is required"
]
}
}

Categories