CakePHP 3.1: Validation for translate behaviour fields (i18n) - validation

I'm trying to add an item including multiple translations in one form with the CakePHP translate behaviour.
How can I validate the translation fields? E.g. make specific languages required?
Let's assume you have a simple items table with a separate translations table items_i18n, set up as described in the book. As simple example the items table has only one field title to translate and I want to save the title in five languages. So I make a form like this (in add view template):
echo $this->Form->create($item, ['controller' => 'Items', 'action' => 'add']);
echo $this->Form->input('title', ['label' => __('English')]);
echo $this->Form->input('_translations.es.title', ['label' => __('Spanish')]);
echo $this->Form->input('_translations.fr.title', ['label' => __('French')]);
echo $this->Form->input('_translations.de.title', ['label' => __('German')]);
echo $this->Form->input('_translations.it.title', ['label' => __('Italian')]);
echo $this->Form->button(__('Save'), ['type' => 'submit']);
echo $this->Form->end();
And saving in the controller (add action/function) like this:
$item = $this->Items->newEntity();
if ($this->request->is('post')) {
$translations = [
'es' => ['title' => $this->request->data['_translations']['es']['title']],
'fr' => ['title' => $this->request->data['_translations']['fr']['title']],
'de' => ['title' => $this->request->data['_translations']['de']['title']],
'it' => ['title' => $this->request->data['_translations']['it']['title']],
];
foreach ($translations as $lang => $data) {
$item->translation($lang)->set($data, ['guard' => false]);
}
$item = $this->Items->patchEntity($item, $this->request->data, ['validate' => 'default'] );
if ( $this->Items->save($item) ) { $this->Flash->success(__('Saved.')); }
else { $this->Flash->error(__('Not saved.')); }
}
$this->set('item', $item);
This is working without validation or if I only have validation rules for the "native" title field (well it should, I simplified the code for stackoverflow and renamed some parts for the example, so maybe there are some typos, but you should get the idea...).
Now let's further assume the languages English (default) and Spanish are required, other language fields are optional. How can I achieve that?
In the ItemsTable I tried something like this for validation:
class ItemsTable extends Table {
public function validationDefault(Validator $validator) {
$validator
// Title English (default field)
->requirePresence('title')
->notEmpty('title', __('Required field'))
// Title Spanish (translate behaviour field)
->requirePresence('_translations.es.title')
->notEmpty('_translations.es.title', __('Required field'))
;
return $validator;
}
}
But this allways brings a validation error "This field is required" because patchEntity($item, $this->request->data); results in the translations being thrown away. I know this by an open issue on GitHub about the saving workflow (btw +1 for this request :).
So currently I'm not sure if there is a way to define validation rules for translation fields when using the CakePHP translation behaviour... Required language fields is only an example, the same problem occurs if you want to validate e.g. the min/max lenght of a input field for a foreign language...

Ok, I think I found a solution. At least temporarily, because I also discovered an issue with NestedValidator and FormHelper.
Currently the validation is still applied to all additional languages. So this is not exactly what I wanted and not the final answer. If you have an idea how I can apply the validation to single languages please leave a comment or answer.
So this is my current intermediate solution with CakePHP 3.1.1:
In the table class add a nested validator for the i18n translation fields.
These nested validation rules will apply to all additional language fields, because they are grouped together in $this->request->data['_translations']:
class ItemsTable extends Table {
public function validationDefault(Validator $validator) {
$validator
// Title English (default language)
->requirePresence('title')
->notEmpty('title')
->add('title', [
'minLength'=>['rule'=>['minLength', 2], 'message' => __('MinLength 2')],
'maxLength'=>['rule'=>['maxLength', 255], 'message' => __('MaxLength 255')],
])
;
// Nested Validation for i18n fields (Translate Behaviour)
// These rules will apply to all 'title' fields in all additional languages
$translationValidator = new Validator();
$translationValidator
->requirePresence('title', 'false') // I want translation to be optional
->allowEmpty('title') // I want translation to be optional
->add('title', [
'minLength'=>['rule'=>['minLength', 5], 'message' => __('MinLength 5')],
'maxLength'=>['rule'=>['maxLength', 255], 'message' => __('MaxLength 255')],
])
;
// Now apply the nested validator to the "main" validation
// ('_translations' is containing the translated input data)
$validator
->addNestedMany('_translations', $translationValidator)
// To prevent "field is required" for the "_translations" data
->requirePresence('_translations', 'false')
->allowEmpty('_translations')
;
return $validator;
}
}
In my test setup I want the translation fields to be optional and have other minLength as the default language. As you can see in the code above, I added allowEmpty and set requirePresence to false for the translation fields. Currently the TranslateBehaviour is still forcing the translation title fields to be required. So I added additionally 'required' => false to the translation input fields in add/edit form:
echo $this->Form->input('_translations.es.title', ['required' => false]);
The separate validation rules are now applied to the translation fields, as shown in the debug result (added temporarily in the controller while testing):
$item = $this->Items->patchEntity($item, $this->request->data);
debug($item);
When you enter only one character in the input fields, the minLength error messages are available in the debug error array.
But currently the FormHelper does not support nested error messages. I reported this issue on GitHub. A temporarily solution to show an error in the form is by checking the error array manually. To do so, add in the Controller:
$item = $this->Items->patchEntity($item, $this->request->data);
if ( !$item->errors() ) {
foreach ($this->request->data['_translations'] as $lang => $data) {
$item->translation($lang)->set($data, ['guard' => false]);
}
}
// Temp workaround for issue#7532:
else {
$this->set('formerrors', $language->errors());
}
And in the add/edit view you can check and use the additional $formerrors array:
if ( isset($formerrors['_translations']['es']['title']) ) { ... }
Another interesting approach is shown in the answer to this question.

Related

FormRequest messages() function does not translate all rules [duplicate]

I'm working on a Laravel 5.8 project and trying to show custom validation messages for a validation which uses the requiredIf validation rule.
Here is how I have it set up:
$validation = Validator::make(
$request->all(),
[
...
'sum' => [
Rule::requiredIf(function() use ($request){
$model = Model::find($request->id);
return $model->is_special; //returns a boolean value
}),
'numeric'
],
...
],
[
...
'sum.required_if' => 'This cannot be blank',
'sum.numeric' => 'Must use a number here',
...
]
);
Now the validation is working correctly and the custom message for the numeric validation shows as should, but the message I get for the requiredIf() method is Laravel's default error message.
I also tried using 'sum.requiredIf' => '...' but that didn't work either and can't seem to find any documentation or example for this scenario.
I was tinkering with this for a while and noticed that for this to work I needed to define
'sum.required' => 'This cannot be blank'
and not 'sum.required_if' => 'This cannot be blank',.
Not sure if this is expected behavior or just a workaround but my deduction is that with the callback Rule::requiredIf(function() use ($request){...}) the parameters :other and :value are not passed so it falls back onto required messaging and I guess this makes sense since required_if and required would not be used on the same :attribute.
Hope this helps anyone who comes across this problem.
First, create a rule name isSpecial or whatever
php artisan make:rule isSpecial
Go to App\Rules\isSpecial.php
private $id;
public function __construct($id) // pass id or what you need
{
//
$this->id=$id;
}
public function passes($attribute, $value) // customize your rules here
{
//
return Model::find($request->id)->is_special;
}
public function message() // here is answer for your question
{
return 'The validation error message.'; // your message
}
in your controller
use App\Rules\isSpecial;
\Validator::make($request->all(), [
'sum' => new isSpecial() ,
])->validate();
another idea :
Specifying Custom Messages In Language Files
In most cases, you will probably specify your custom messages in a language file instead of passing them directly to the Validator. To do so, add your messages to custom array in the resources/lang/xx/validation.php language file.
'custom' => [
'email' => [
'required' => 'We need to know your e-mail address!',
],
],
Simple notice:
- I suggest using HTTP Requests instead use validation in your controller and function direct
Looks like as of Laravel 8, using required_if works as expected, and alternatively will not fall back on required as mentioned previously:
'sum.required_if' => 'This cannot be blank',

Custom error message for `requiredIf` validation in laravel

I'm working on a Laravel 5.8 project and trying to show custom validation messages for a validation which uses the requiredIf validation rule.
Here is how I have it set up:
$validation = Validator::make(
$request->all(),
[
...
'sum' => [
Rule::requiredIf(function() use ($request){
$model = Model::find($request->id);
return $model->is_special; //returns a boolean value
}),
'numeric'
],
...
],
[
...
'sum.required_if' => 'This cannot be blank',
'sum.numeric' => 'Must use a number here',
...
]
);
Now the validation is working correctly and the custom message for the numeric validation shows as should, but the message I get for the requiredIf() method is Laravel's default error message.
I also tried using 'sum.requiredIf' => '...' but that didn't work either and can't seem to find any documentation or example for this scenario.
I was tinkering with this for a while and noticed that for this to work I needed to define
'sum.required' => 'This cannot be blank'
and not 'sum.required_if' => 'This cannot be blank',.
Not sure if this is expected behavior or just a workaround but my deduction is that with the callback Rule::requiredIf(function() use ($request){...}) the parameters :other and :value are not passed so it falls back onto required messaging and I guess this makes sense since required_if and required would not be used on the same :attribute.
Hope this helps anyone who comes across this problem.
First, create a rule name isSpecial or whatever
php artisan make:rule isSpecial
Go to App\Rules\isSpecial.php
private $id;
public function __construct($id) // pass id or what you need
{
//
$this->id=$id;
}
public function passes($attribute, $value) // customize your rules here
{
//
return Model::find($request->id)->is_special;
}
public function message() // here is answer for your question
{
return 'The validation error message.'; // your message
}
in your controller
use App\Rules\isSpecial;
\Validator::make($request->all(), [
'sum' => new isSpecial() ,
])->validate();
another idea :
Specifying Custom Messages In Language Files
In most cases, you will probably specify your custom messages in a language file instead of passing them directly to the Validator. To do so, add your messages to custom array in the resources/lang/xx/validation.php language file.
'custom' => [
'email' => [
'required' => 'We need to know your e-mail address!',
],
],
Simple notice:
- I suggest using HTTP Requests instead use validation in your controller and function direct
Looks like as of Laravel 8, using required_if works as expected, and alternatively will not fall back on required as mentioned previously:
'sum.required_if' => 'This cannot be blank',

CakePHP Validate a specific rule only when a couple required fields aren't empty

I wrote a custom rule method for validating whether a record exists in the DB before adding a new record. I put the method in a behavior so I could share it with other models, but I've run into a chicken and egg situation.
In order to know whether a category has a specific group name already I need to have the category id, and the group name. So I pass those keys through using my custom rule (category_id and name). But, this won't work since if I don't choose a category_id by mistake then the query will occur on just the name, so I patched this with a couple lines, but need to return true if this is the case and bank on the category_id validation being invalid.
Is there a better way to implement this kind of validation? Is this not as bad as I think? Or just don't bother and in my controller drop hasAny() under my call to validates() if it passes.
MODEL:
public $validate = [
'category_id' => [
'rule' => 'notEmpty',
'message' => 'Category is required.'
],
'name' => [
'notEmpty' => [
'rule' => 'notEmpty',
'message' => 'Team is required.'
],
'recordExists' => [
'rule' => [ 'recordExists', [ 'category_id', 'name' ] ],
'message' => 'Group already exists.'
]
]
];
// BEHAVIOR:
public function recordExists( Model $Model, $conditions, $requireKeys )
{
// Overrite conditions to
$conditions = $Model->data[ $Model->name ];
// Trim all array elements and filter out any empty indexes
$conditions = array_map( 'trim', $conditions );
$conditions = array_filter( $conditions );
// Get the remaining non-empty keys
$conditionKeys = array_keys( $conditions );
// Only query for record if all required keys are in conditions
if (empty( array_diff( $requireKeys, $conditionKeys ) )) {
return !$Model->hasAny( $conditions );
}
// NOTE: seems wrong to return true based on the assumption the category_id validation has probably failed
return true;
}
Use the beforeValidate() callback of the model to check if the fields are present and if they're empty. If they're empty just unset() the recordExists validation rule in your models validation property. Copy them to a temporary variable or property in the case you want to set them back after your current operation.
And use $Model->alias, name will break if the model is used through an association that has a different name.
$conditions = $Model->data[ $Model->name ];

CakePHP Validation nonBlank fails Whether the Field is Empty or !Empty

The docs say that 'notBlank' is a validation rule for fields that you want to make sure they are not empty, as in !empty($somevalue), but when I leave the field blank ('') or when I put a value in the field ('s0meCraZyPasSworD') it still display the error message?
Can anyone see what I'm doing wrong? The rest of the validations work like minlength, but I commented them out to get a better idea of why 'notBlank' doesn't appear to be working...
CONTROLLER:
// Set of validation rules to be run
$validateRules = [
'fieldList' => [
'currentpassword',
'newpassword',
'confirmpassword'
]
];
if ($this->Admin->validates( $validateRules )) {
...
}
MODEL:
class Admin extends AppModel
{
public $name = 'Admin';
public $validate = [
'currentpassword' => [
'notBlank' => [
'rule' => 'notBlank',
'message' => 'Current password is required.'
]
],
...
You tagged CakePHP 2.4 - notBlank was added in 2.7 so you have to use notEmpty or set allowEmpty to false...
The data sent to the model’s save() method must contain data for the
login field. If it doesn’t, validation will fail. The default value
for this key is boolean false.
required => true does not mean the same as the validation rule
notBlank(). required => true indicates that the array key must be
present - it does not mean it must have a value
From the official website:
http://book.cakephp.org/2.0/en/models/data-validation.html#required
So you will need another rule like the allowEmpty to validate this field and not the notBlank rule.

Using form request specific custom validation attributes

Using Laravel's localization (http://laravel.com/docs/5.1/localization) I have created some custom validation attributes to provide friendlier validation errors (for instance, 'First Name' instead of first name etc).
I am using form requests (http://laravel.com/docs/5.1/validation#form-request-validation) in order to validate user submissions and there are scenarios where I would like to provide store-specific custom validation attributes (for instance, I may have a 'name' field that is Brand Name in one context, and Product Name in another).
The messages() method allows me to specify validation rule specific message overrides, but that isn't ideal as it's not the validation message as such we need to override, just the attribute name (for example, if we have 5 validation rules for 'email', we have to provide 5 overrides here, rather than one override for, let's say, Customer Email).
Is there a solution to this? I note references to formatValidationErrors() and formatErrors() in the Laravel documentation, but there is not really any information on how to correctly override these, and I've not had much luck in trying.
You can override the attribute names, which is defaulting to whatever the field name is.
With form request
In your form request class override the attributes() method:
public function attributes()
{
return [
'this_is_my_field' => 'Custom Field'
];
}
With controller or custom validation
You can use the 4th argument to override the field names:
$this->validate($request, $rules, $messages, $customAttributes);
or
Validator::make($data, $rules, $messages, $customAttributes);
Simple working example
Route::get('/', function () {
// The data to validate
$data = [
'this_is_my_field' => null
];
// Rules for the validator
$rules = [
'this_is_my_field' => 'required',
];
// Custom error messages
$messages = [
'required' => 'The message for :attribute is overwritten'
];
// Custom field names
$customAttributes = [
'this_is_my_field' => 'Custom Field'
];
$validator = Validator::make($data, $rules, $messages, $customAttributes);
if ($validator->fails()) {
dd($validator->messages());
}
dd('Validation passed!');
});
As detailed in my question, I was looking for a way to provide specific form request stores (http://laravel.com/docs/5.1/validation#form-request-validation) with custom attribute names.
The Laravel documentation only covers two methods for Requests in this context - rules() and authorize(). I was aware there is a messages() method to provide validation specific custom error messages, but it also appears there is an attributes() method, which fits my requirements exactly:
public function attributes()
{
return [
'name' => 'Product Name'
]
}
This overrides the attribute name in the context of my store.

Resources