I would like to figure out how to create your own error messages for your rules (e.g. regular expression rules) to be reused by both server PHP and client javascript (using jqueryvalidation through laravel-jsvalidation )
I have tried and can not make it work, and below is a small example to show what I am trying to do but it does not work.
What am I doing wrong?
My small example:
In the file "routes\web.php":
Route::get('/minimal_example_laravel_jsvalidation', function() {
// Of course these rules should not really be defined here since
// the purpose of the rules is to also reuse them from PHP Laravel code
// but my problem is now how to generate javascript that can
// reuse the same rules and therefore I just put the rules and messages
// here in this minimalistic example illustrating the problem
$rules = [
'three_digits' => 'required|regex:/^\d{3}$/'
];
$messages = [
'three_digits' => 'Must be exactly three digits'
];
$validator = JsValidator::make($rules, $messages);
return view('minimal_example_laravel_jsvalidation')->with("validator", $validator);
});
In the file "resources\views\minimal_example_laravel_jsvalidation.blade.php":
...
{!! $validator->selector('#myForm') !!}
...
When using the URL
http://localhost:8000/minimal_example_laravel_jsvalidation
with the web browser and then "view source" I can see that the following javascript code has been generated by the above "$validator->selector" :
jQuery(document).ready(function(){
$("#myForm").each(function() {
$(this).validate({
errorElement: 'span',
errorClass: 'invalid-feedback',
errorPlacement: function (error, element) {
if (element.parent('.input-group').length ||
element.prop('type') === 'checkbox' || element.prop('type') === 'radio') {
error.insertAfter(element.parent());
// else just place the validation message immediately after the input
} else {
error.insertAfter(element);
}
},
highlight: function (element) {
$(element).closest('.form-control').removeClass('is-valid').addClass('is-invalid'); // add the Bootstrap error class to the control group
},
unhighlight: function(element) {
$(element).closest('.form-control').removeClass('is-invalid').addClass('is-valid');
},
success: function (element) {
$(element).closest('.form-control').removeClass('is-invalid').addClass('is-valid'); // remove the Boostrap error class from the control group
},
focusInvalid: true,
rules: {"three_digits":{"laravelValidation":[["Required",[],"The three digits field is required.",true],["Regex",["\/^\\d{3}$\/"],"The three digits format is invalid.",false]]}} });
});
});
Indeed, the error message I get when not writing three digits in the field through my web browser, is as above "The three digits format is invalid." while I expect that it instead should be "Must be exactly three digits" as I defined in the "$messages" array.
I have seen that it is possible with Laravel to create PHP classes with "Custom Validation Rules" where you also can define custom messages, but as far as I understand, if you use those custom rules with laravel-jsvalidation then the validation must be done with AJAX instead of javascript validation directly within the browser, which is what I want to do instead of doing AJAX calls.
I am using these versions:
laravel/framework v7.4.0
proengsoft/laravel-jsvalidation 3.0.0
Try changing messages array like this,
$messages = [
'three_digits.required' => 'Three Digits field is required',
'three_digits.regex' => 'Must be exactly three digits'
];
Note I have added rule also to the message key. ('three_digits.required')
Related
We are building an api endpoint where precision is required. We want to enforce strict validation on the parameters that are POST/PUT to the server.
If the api user sends a key=value pair that is not supported (eg. we allow the parameters [first_name, last_name] and the user includes an unsupported parameter [country]), we want the validation to fail.
Have tried building a custom validator called allowed_attributes (used as allowed_attributes:attr1,attr2,...), but for it to be usable in a $validationRules array, it has to be applied to the parent of a list of nested/child attributes (...because otherwise our custom validator did not have access to the attributes being validated).
Validator::extend('allowed_attributes', 'App\Validators\AllowedAttributesValidator#validate');
This created issues with other validators, where we then had to anticipate this parent/child structure and code around it, including additional post-validation clean-up of error keys and error message strings.
tl;dr: very dirty, not a clean implementation.
$validationRules = [
'parent' => 'allowed_attributes:first_name,last_name',
'parent.first_name' => 'required|string|max:40',
'parent.last_name' => 'required|string|max:40'
];
$isValid = Validator::make(['parent' => $request], $validationRules);
var_dump("Validation results: " . ($isValid ? "passed" : "failed"));
Any ideas/suggestions on how this can be accomplished more cleanly in laravel, without requiring the use of parent/child relationship to get access to the list of all $request attributes (within the custom validator)?
I preferred to post a new answer as the approach is different from the previous one and a bit more cleaner. So I would rather keep the two approaches separated and not mixed together in the same answer.
Better problem handling
After digging deeper into the Validation's namespace's source code since my last answer I figured out that the easiest way would have been to extend the Validator class to remplement the passes() function to also check what you needed.
This implementation has the benefit to also correcly handle specific error messages for single array/object fields without any effor and should be fully compatible with the usual error messages translations.
Create a custom validator class
You should first create a Validator class within your app folder (I placed it under app/Validation/Validator.php) and implement the passes method like this:
<?php
namespace App\Validation;
use Illuminate\Support\Arr;
use Illuminate\Validation\Validator as BaseValidator;
class Validator extends BaseValidator
{
/**
* Determine if the data passes the validation rules.
*
* #return bool
*/
public function passes()
{
// Perform the usual rules validation, but at this step ignore the
// return value as we still have to validate the allowance of the fields
// The error messages count will be recalculated later and returned.
parent::passes();
// Compute the difference between the request data as a dot notation
// array and the attributes which have a rule in the current validator instance
$extraAttributes = array_diff_key(
Arr::dot($this->data),
$this->rules
);
// We'll spin through each key that hasn't been stripped in the
// previous filtering. Most likely the fields will be top level
// forbidden values or array/object values, as they get mapped with
// indexes other than asterisks (the key will differ from the rule
// and won't match at earlier stage).
// We have to do a deeper check if a rule with that array/object
// structure has been specified.
foreach ($extraAttributes as $attribute => $value) {
if (empty($this->getExplicitKeys($attribute))) {
$this->addFailure($attribute, 'forbidden_attribute', ['value' => $value]);
}
}
return $this->messages->isEmpty();
}
}
This would essentially extend the default Validator class to add additional checks on the passes method. The check compute the array difference by keys between the input attributes converted to dot notation (to support array/object validation) and the attributes which have at least one rule assigned.
Replace the default Validator in the container
Then the last step you miss is to bind the new Validator class in the boot method of a service provider. To do so you can just override the resolver of the Illuminate\Validation\Factory class binded into the IoC container as 'validator':
// Do not forget the class import at the top of the file!
use App\Validation\Validator;
// ...
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->app->make('validator')
->resolver(function ($translator, $data, $rules, $messages, $attributes) {
return new Validator($translator, $data, $rules, $messages, $attributes);
});
}
// ...
Pratical use in a controller
You don't have to do anything specific to use this feature. Just call the validate method as usual:
$this->validate(request(), [
'first_name' => 'required|string|max:40',
'last_name' => 'required|string|max:40'
]);
Customize Error messages
To customize the error message you just have to add a translation key in your lang file with a key equal to forbidden_attribute (you can customize the error key name in the custom Validator class on the addFailure method call).
Example: resources/lang/en/validation.php
<?php
return [
// ...
'forbidden_attribute' => 'The :attribute key is not allowed in the request body.',
// ...
];
Note: this implementation has been tested in Laravel 5.3 only.
It should work for simple key/value pairs with this custom validator:
Validator::extendImplicit('allowed_attributes', function ($attribute, $value, $parameters, $validator) {
// If the attribute to validate request top level
if (strpos($attribute, '.') === false) {
return in_array($attribute, $parameters);
}
// If the attribute under validation is an array
if (is_array($value)) {
return empty(array_diff_key($value, array_flip($parameters)));
}
// If the attribute under validation is an object
foreach ($parameters as $parameter) {
if (substr_compare($attribute, $parameter, -strlen($parameter)) === 0) {
return true;
}
}
return false;
});
The validator logic is pretty simple:
If $attribute doesn't contains a ., we're dealing with a top level parameter, and we just have to check if it is present in the allowed_attributes list that we pass to the rule.
If $attribute's value is an array, we diff the input keys with the allowed_attributes list, and check if any attribute key has left. If so, our request had an extra key we didn't expect, so we return false.
Otherwise $attribute's value is an object we have to check if each parameter we're expecting (again, the allowed_attributes list) is the last segment of the current attribute (as laravel gives us the full dot notated attribute in $attribute).
The key here is to apply it to validation rules should like this (note the first validation rule):
$validationRules = [
'parent.*' => 'allowed_attributes:first_name,last_name',
'parent.first_name' => 'required|string|max:40',
'parent.last_name' => 'required|string|max:40'
];
The parent.* rule will apply the custom validator to each key of the 'parent' object.
To answer your question
Just don't wrap your request in an object, but use the same concept as above and apply the allowed_attributes rule with a *:
$validationRules = [
'*' => 'allowed_attributes:first_name,last_name',
'first_name' => 'required|string|max:40',
'last_name' => 'required|string|max:40'
];
This will apply the rule to all the present top level input request fields.
NOTE: Keep in mind that laravel validation is influenced by order of the rules as they are putted in rules array.
For example, moving the parent.* rule on bottom will trigger that rule on parent.first_name and parent.last_name; as opposed, keeping it as the first rule will not trigger the validation for the first_name and last_name.
This means that you could eventually remove the attributes that has further validation logic from the allowed_attributes rule's parameter list.
For example, if you would like to require only the first_name and last_name and prohibit any other field in the parent object, you might use these rules:
$validationRules = [
// This will be triggered for all the request fields except first_name and last_name
'parent.*' => 'allowed_attributes',
'parent.first_name' => 'required|string|max:40',
'parent.last_name' => 'required|string|max:40'
];
But, the following WON'T work as expected:
$validationRules = [
'parent.first_name' => 'required|string|max:40',
'parent.last_name' => 'required|string|max:40',
// This, instead would be triggered on all fields, also on first_name and last_name
// If you put this rule as last, you MUST specify the allowed fields.
'parent.*' => 'allowed_attributes',
];
Array Minor Issues
As far as I know, per Laravel's validation logic, if you were up to validate an array of objects, this custom validator would work, but the error message you would get would be generic on the array item, not on the key of that array item that wasn't allowed.
For example, you allow a products field in your request, each with an id:
$validationRules = [
'products.*' => 'allowed_attributes:id',
];
If you validate a request like this:
{
"products": [{
"id": 3
}, {
"id": 17,
"price": 3.49
}]
}
You will get an error on product 2, but you won't be able to tell which field is causing the problem!
I use Laravel built-in validator and I want to get the first error message
if ($validator->fails()) {
$error = $validator->messages()->toJson();
.....
}
This is the result when I print error
{"name":["The name must be at least 5 characters."],"alamat":["The address must be at least 5 characters."]}
In the example above, I want to get the first error, which is "The name must be at least 5 characters.". How can I do that?
Try this:
if ($validator->fails()) {
$error = $validator->errors()->first();
}
As per 2019 Laravel 5.8 and above to get all the error messages from the validator is as easy as this:
// create the validator and make a validation here...
if ($validator->fails()) {
$fieldsWithErrorMessagesArray = $validator->messages()->get('*');
}
You will get the array of arrays of the fields' names and error messages. Something like this:
[
'price'=>
[
0 => 'Price must be integer',
1 => 'Price must be greater than 0'
]
'password' => [
[
0 => 'Password is required'
]
]
]
You can use other validation messages getters that Illuminate\Support\MessageBag class provides (it is actually the object type that $validator->messages() above returns).
Message Bag Error Messages Additional Helpers
Go to your_laravel_project_dir/vendor/illuminate/support/MessageBag.php and find some useful methods like keys, has, hasAny, first, all, isEmpty etc. that you may need while checking for particular validation errors and customizing HTTP response messages.
It is easy to understand what they do by the look at the source code. Here is the Laravel 5.8 API reference though probably less useful than the source code.
In your ajax request, when you get the data, try data.name.
This will give you the error message for the name field.
$.ajax({
url: "/your-save-url",
type: "post",
data: serializedData,
success: function(data) { alert(data.name)}
});
If validation fails, the withErrors method can be used to flash the error messages to the session. This is an array and this method will automatically share $errors with all views after redirection.
return redirect('register')->withErrors($validator, 'login');
The MessageBag can be accessed using the instance from the $errors variable:
{{ $errors->login->first('email') }}
Form API docs
Hope this is helpful.
If you are using toastr style error displaying, this will work:
#if(session()->get('errors'))
toastr.error("{{ session()->get('errors')->first() }}");
#endif
for getting all errors, try this:
if ($validator->fails()) {
$error = $validator->errors()->all();
}
if you want to do it inside the controller you can:
Arr::first(Arr::flatten($validator->messages()->get('*')))
you will get the first text message
The email must be accepted.
I have a duration field that sometimes can be empty and sometimes can't, depending on the other data sent by the form. So I'm trying to do custom validation in CakePHP3.
In my table I did
public function validationDefault(Validator $validator)
{
$validator
->add('duration', 'durationOk', [
'rule' => 'isDurationOk',
'message' => 'duration is not OK',
'provider' => 'table'
]);
return $validator;
}
public function isDurationOk($value, $context)
{
// do some logic
return false; // Always return false, just for test
}
Now when I set the value for duration field I get an 'duration is not OK' error (as expected). But when I let the value empty I get a 'This field cannot be left empty' error.
So I added:
->allowEmpty('duration');
But in this case when duration is empty I don't get an error at all.
Am I doing something wrong or it's just me don't understanding how validation works?
Let me read the book for you:
Conditional Validation
When defining validation rules, you can use the on key to define when
a validation rule should be applied. If left undefined, the rule will
always be applied. Other valid values are create and update. Using one
of these values will make the rule apply to only create or update
operations.
Additionally, you can provide a callable function that will determine
whether or not a particular rule should be applied:
'on' => function ($context) {
// Do your "other data" checks here
return !empty($context['data']['other_data']);
}
So just define the conditions depending on your "other data" in the callback to apply the rule only when the conditons are true.
Alternatively you can manipulate the plain form data even before it gets validated in the beforeMarshal() callback of the table and change the form data as needed or load another validator or modify the validator.
Currently the Validator in Laravel only appears to return one error message per field, despite the field potentially having multiple rules and messages. (Note: I'm currently passing an empty array as $data to Validator::make)
What I'm trying to do is build an array of each field's rules and messages that could potentially be re-used for front end validation. Something like this:
{
"name": {
"required": [
"The name field is required."
],
"max:255": [
"The name field may not be greater than 255."
]
},
"email": {
"required": [
"The email field is required."
],
"email": [
"The email field must be a valid email address."
],
"max:255": [
"The email field may not be greater than 255."
]
}
}
The getMessage method in Illuminate\Validation\Validator looks like it would get me close to being able to construct something myself, however it is a protected method.
Does anyone know of a way to get a Validator instance to output all rules and messages?
Currently the Validator in Laravel only appears to return one error message per field, despite the field potentially having multiple rules and messages.
Validation of given field stops as soon as a single validation rule fails. That's the reason you're getting only single error message per field.
As of fetching the validation messages like in the example you provided, Laravel's validator does not provide such option, but you could easily achieve that by extending the Validator class.
First, create your new class:
<?php namespace Your\Namespace;
use Illuminate\Validation\Validator as BaseValidator;
class Validator extends BaseValidator {
public function getValidationMessages() {
$messages = [];
foreach ($this->rules as $attribute => $rules) {
foreach ($rules as $rule) {
$messages[$attribute][$rule] = $this->getMessage($attribute, $rule);
}
}
return $messages;
}
}
As you can see the output is a bit different than your example. There is no need to return an array of messages for given attribute and rule, as there will be always only one message in the array, so I'm just storing a string there.
Second, you need to make sure that your validator class is used. In order to achieve that, you'll need to register your own validator resolver with Validator facade:
Validator::resolver(function($translator, array $data, array $rules, array $messages, array $customAttributes) {
return new \Your\Namespace\Validator($translator, $data, $rules, $messages, $customAttributes);
});
You can do this in your AppServiceProvider::boot() method.
Now, in order to get validation messages for given validator, you just need to call:
Validator::make($data, $rules)->getValidationMessages();
Keep in mind this code hasn't been tested. Let me know if you see any issues or typos with the code and I'll be more than happy to get that working for you.
I have a form (defined in XML) which is used with Joomla's JForm to handle. What I'd like to know is if it's possible to validate against multiple rules at once.
Typically, I've come to understand that Joomla's JForm accepts only one rule for validation, defined in the XML of the form:
Joomla's JForm internals also seem to suggest I can't, the following area being the only one I can find handing validation:
// Get the field validation rule.
if ($type = (string) $element['validate'])
{
// Load the JFormRule object for the field.
$rule = $this->loadRuleType($type);
// If the object could not be loaded return an error message.
if ($rule === false)
{
throw new UnexpectedValueException(sprintf('%s::validateField() rule `%s` missing.', get_class($this), $type));
}
// Run the field validation rule test.
$valid = $rule->test($element, $value, $group, $input, $this);
// Check for an error in the validation test.
if ($valid instanceof Exception)
{
return $valid;
}
}
This isn't wrapped in a loop, so I'm quite concerned that I can't apply multiple rules at once to a particular field.
Are you looking for server or client side validation? Sean's answer seems to cover server side so I figured I'd add some insight into client side techniques.
You enable client side validation two ways. The first and simplest would be by adding the following to your form field definition, which would ensure any required fields are filled out to proceed.
required="true"
Second would be to add a class to the form field definition to let Joomla core know you want to validate the field and how. Joomla offers 4 validations built into the core: validate-username, validate-password, validate-numeric and validate-email.
These in and of themselves don't help you much, but the ability to create and reference custom client-side validations does. For my example we're going to ensure a check box is marked before allowing the form to submit. So in the form field definition I'll add:
class="validate-checked"
On the page where you render the form, be sure to load the JS library for validation using:
JHtml::_('behavior.formvalidation');
In addition, add the class form-validate to your form HTML element.
Add this javascript to handle the actual validation, here I have a checkbox input type with an ID of tos I'm verifying. In the setHandler method, the first parameter is the custom name I entered in the form field definition class statement, validate-checked:
<script>
window.addEvent('domready', function(){
document.formvalidator.setHandler('checked', function(value) {
var tos = document.getElementById('tos');
if (tos.checked) {
return true;
} else {
return false;
}
});
});
</script>
Now, capture the submit event and verify all core and custom validations passed before submitting the form.
Joomla.submitbutton = function(task) {
if (task == 'user.cancel' || document.formvalidator.isValid(document.id(".myFormId"))) {
Joomla.submitform(task, document.getElementById('myformId'));
}
You can create as many custom client-side validation scripts as you want. Inside the setHandler method you can interact with the DOM and use the passed in value parameter to determine if the field should pass, only needing to worry about returning true or false to indicate results and Joomla will handle the rest. So you can either create one complicated validation or many smaller concise validations to suit your needs.
Hope that helps...
This is a common request. There are a few possibilities. You could write your own JFormRule with more complex validation. The other is that you could programatically add an attribute to the field that runs the additional validation sort of like what Sean is advocating.
This answer assumes that it is not possible to natively add multiple rules on one field.
Assuming that it is not possible to apply multiple rules to one field natively, then it may be possible to extend JForm::validateField() to enable such a feature by simply calling the validate method for each validation rule found.
// Extending class JForm
protected function validateField(SimpleXMLElement $element, $group = null, $value = null, JRegistry $input = null) {
if($type = (string) $element['validate'])
{
$multiple_types = explode('|', $type);
if(is_array($multiple_types) && $multiple_types[0] !== $type)
{
foreach($multiple_types as $single_type)
{
$result = parent::validateField($element, $group, $value, $input);
// Validation failed, return the result and stop validating.
if($result !== true)
{
return $result;
}
}
return true;
}
else
{
return parent::validateField($element, $group, $value, $input);
}
}
}
With that example, validation rules could be structured like:
validate="rule1|rule2"