Yii2 - Generate common error in validation (not related with particular attribute) - validation

I would like to apply a validator for ActiveRecord but not for particular field, I mean, I would like see the validator in the error summary of the form but not associated to a particular field.

When writing a custom validation, you need to use addError method of yii\base\Model (if you write it in model) or yii\validators\Validator (if you write separate validator).
Both methods require $attribute parameter (attribute name) to be passed, and as you can see from the sources, you can't leave it blank:
addError of yii\base\Model:
/**
* Adds a new error to the specified attribute.
* #param string $attribute attribute name
* #param string $error new error message
*/
public function addError($attribute, $error = '')
{
$this->_errors[$attribute][] = $error;
}
addError of yii\validators\Validator:
/**
* Adds an error about the specified attribute to the model object.
* This is a helper method that performs message selection and internationalization.
* #param \yii\base\Model $model the data model being validated
* #param string $attribute the attribute being validated
* #param string $message the error message
* #param array $params values for the placeholders in the error message
*/
public function addError($model, $attribute, $message, $params = [])
{
$params['attribute'] = $model->getAttributeLabel($attribute);
if (!isset($params['value'])) {
$value = $model->$attribute;
if (is_array($value)) {
$params['value'] = 'array()';
} elseif (is_object($value) && !method_exists($value, '__toString')) {
$params['value'] = '(object)';
} else {
$params['value'] = $value;
}
}
$model->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language));
}
Possible options:
1) Select the most important relevant field and add error to it.
2) Select multiple important relevant fields and add the same error message to them (you can store and pass message in separate variable before passing to keep code DRY).
3) You can use not existing attribute name for adding error, let's say all, because attribute existence is not checked at that point.
class YourForm extends \yii\base\Model
{
/**
* #inheritdoc
*/
public function rules()
{
return [
['name', 'yourCustomValidationMethod'],
];
}
/**
* #return boolean
*/
public function yourCustomValidationMethod()
{
// Perform your custom validation here regardless of "name" attribute value and add error when needed
if (...) {
$this->addError('all', 'Your error message');
}
}
}
Note that you still need to attach validator to existing attribute(s) (otherwise exception will be thrown). Use the most relevant attribute.
As a result, you will see error only in error summary. You can display error summary in form like that:
<?= $form->errorSummary($model) ?>
But in most cases there are always one or multiple attributes related with error so I recommend to use option 1 or 2. Option 3 is kind of hack but I think still pretty elegant solution for your problem.

Related

Check validation if other field has value as well as check field has specific value in Laravel

I am doing a project in Laravel and adding some validation rule on my form. Here, I have situation where I a field "A" is required if field "B" has specific value. And after field "A" is required then I want to validate value of field "A".
I am trying to do achieve the same using "required_if" validation rule as given below:
['years' => 'required_if:duration_type,0,gt:0'];
Here I want to check if duration type is 0 then my "years" field must be required and value just greater than 0.
Can someone please guide me on how can I achieve the same?
Thanks in Advance
You can write a DataAwareRule Rule as laravel's doc explained here.
I wrote a sample rule for you that implemented this interface, and retrieves for example b parameter and assigns it to b property of rule object, then you can use it in passes() method.
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\Rule;
class ARule implements Rule, DataAwareRule
{
protected $b;
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
if ($this->b == 'specific_value') {
if ($value == 'valid_value') {
return true;
}
}
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The validation error message.';
}
public function setData($data)
{
$this->b = data_get($data, 'b');
}
}
https://laravel.com/docs/8.x/validation#conditionally-adding-rules
[years => exclude_unless:duration_type,0,required,gt:0];
Here it is,
['years' => 'exclude_unless:duration_type,==,0|required|numeric|gt:0'];
exclude_unless will exclude the rule unless duration_type is 0. When duration_type is 0, years will be required. And gt rule should work properly here when it is numeric.

How to set only one NULLABLE out of two input fields

I have two input fields one as mobile_no and other as email, i want to set a condition that out of two fields if mobile_no is not given them email must be required and vice versa.
How to set nullable condition on laravel validation that only one field out of two must be required.
You can create a custom rule. So your need is opposite of what required_if rule does, so please try this one and let me know.
First create custom rule, I call it NullableIf
php artisan make:rule NullableIf
Then the implementation should be something like this:
class NullableIf implements Rule
{
private $otherField;
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct( $otherField )
{
$this->otherField = $otherField;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
if($this->otherField === null)
{
return $value !== null;
}
return true;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The validation error message.';
}
}
To use it you do:
$request->validate([
'field1' => [ new NullableIf(request('field2')) ],
'field2' => [ new NullableIf(request('field1')) ]
]);
I hope at least it gives you direction unless this exact implementation does not suits your needs.

Laravel validation using class

I am trying to use Laravel validation : as described here
I am not going to place entire rule class, as it is straight from artisan make:rule.
PROBLEM:
When I try to test 'false' here:
function passes($attribute, $value)
{
return false; //testing 'false' logs me out
}
This is how I use it in controller:
$this->validate($request, [
'image_64' => ['required', new \App\Rules\Base64Rule]
]);
It is like redirect isn't going to form page, but index page - which in my case logs user out (among other things).
Is there any protected var to mark proper redirect route, when method 'passes' returns false?
Anyone came across something like this?
EDIT
Sam,
As I mentioned, rule class is pristine - freshly generated:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Base64Rule implements Rule
{
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct()
{
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
return false;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The validation error message.';
}
}
If I run on top of my controller anything like:
use App\Rules\Base64Rule;
$rule = new Base64Rule;
dd($rule->passes('image_64', 'udfshjbfisuusoshgosghosh'));
I get whatever I have in rule's method passes().
If I use in rule's method passes() return true; it works right.
If I use in rule's method passes() dd('some'), I get 'some' in that controller test.
When test is 'false' and regular validation code is used:
use App\Rules\Base64Rule;
$this->validate($request, [
'image_64' => ['required', new Base64Rule]
]);
... I get redirected to index page.
I have to follow request/response and see, where Laravel fails and why ... I guess. And is it my code, or a bug.

Validator event dispatched before Entity validation starts

Question
Is it possible in Symfony 2.8+ / 3.x+ to dispatch event before starting entity validation?
Situation:
Let's say we have 100 entities, they have #LifeCycleCallbacks, they have #postLoad Event that do something, but the result of this is only used for valiation of Entity, in 99% of situations result of #postLoad is unimportant for system. So if we have hundrets or thousands of Entities fetched from DB there will be a lot of machine-cycles lose for unimportant data.
It would be nice to run some kind of event, that will run method, that will populate that data for that specific Entity, just before validations starts.
instead of:
$entity->preValidate();
$validator = $this->get('validator');
$errors = $validator->validate($entity);
we could have:
$validator = $this->get('validator');
$errors = $validator->validate($entity);
And in validate() situation, preValidate() will be dispatched autmaticly as Event (of course with check if Entity does have such method).
CaseStudy:
I have a system that stores pages/subpages as entities. There can be 10 or 10000 pages/subpages
Pages/subpages can have file.
Entities stores only files names (becouse we can't store SplFileInfo - resource serialization restriction)
While Entity->file property is type of string, when I want to make it to be instance of File (so we can do validation of type File) I have something like:
/**
* #postLoad()
*/
public function postLoad()
{
//magicly we get $rootPath
$this->file = new File($rootPath . '/' . $this->file);
}
/**
* #prePersist()
* #preUpdate()
*/
public function preSave()
{
if ($this->file instance of File)
$this->file = $this->file->getFilename();
}
}
Ok, but postLoad() will CHANGE the property, and Doctrine will NOTICE that. So in next
$entityManager->flush()
ALL entities will be flushed - even if preSave() will change it back to be just string as it was before.
So if I have any other entity, let's say TextEntity, and I want to remove it
$entityManager->remove($textEntity);
$entityManager->flush();
All other Entities that are somehow changed (change was noticed by Doctrine), are flushed, no matter if value of file property is the same as in DB (and change was only temporary).
It will be flushed.
So we have hundrets, or thousends of pointless sql updates.
Btw.
1. ->flush($textEntity) will throw Exception, becouse ->remove($textEntity) have already "deleted" that entity.
2. Entity property ->file must be of type File for Assert/File, becouse FileValidator can only accept values of File or absolute-path-to-file.
But I will NOT store absolute-path-to-file, becouse it will be completly different on Dev, Stage, and Production environments.
This is problem that occured when I tried to make file uploading as it was described in Symfony cookbook http://symfony.com/doc/current/controller/upload_file.html.
My solution was to, in postLoad(), create File instance in property that is not Doctrine column, and is anoted to have assertion, etc.
That works, but the problem of useless postLoad()s stays, and i thought about events. That could be elastic, and very elegant solution - instead of controllers getting "fat".
Any one have better solution? Or know how to dispatch event if ->validate() happends?
Hello Voult,
Edit: first method is deprecated in symfony 3 as the thread op mentioned in a comment. Check the second method made for symfony 3.
Symfony 2.3+,Symfony < 3
What I do in this cases, since symfony and most other bundles are using parameters for service class definition, is to extend that service. Check the example below and for more information on extending services check this link
http://symfony.com/doc/current/bundles/override.html
First you need to add a some marker to your entities that require pre-validation. I usually use interfaces for stuff like this something like
namespace Your\Name\Space;
interface PreValidateInterface
{
public function preValidate();
}
After this you extend the validator service
<?php
namespace Your\Name\Space;
use Symfony\Component\Validator\Validator;
class MyValidator extends Validator //feel free to rename this to your own liking
{
/**
* #inheritdoc
*/
public function validate($value, $groups = null, $traverse = false, $deep = false)
{
if (is_object($value) && $value instanceof PreValidateInterface) {
$value->preValidate();
}
return parent::validate($value, $groups, $traverse, $deep);
}
}
And final step, you need to add the class value parameter to your 'parameters' config block in config.yml, something like this:
parameters:
validator.class: Your\Name\Space\MyValidator
This is the basic idea. Now you can mix end match this idea with whatever you want to achieve. For instance instead of calling a method on the entity (I usually like to keep business logic outside of my entities), you can look for the interface and if it is there you can launch a pre.validate event with that entity on it, and use a listener to do the job. After that you can keep the result from parent::validate and also launch a post.validate event. You see where i'm going with this. You basically can do whatever you like now inside that validate method.
PS: The example above is the easy method. If you want to go the event route, the service extension will be harder, since you need to inject the dispatcher into it. Check the link I provided at the beginning to see the other way to extend a service and let me know if you need help with this.
For Symfony 3.0 -> 3.1
In this case they managed to make it hard and dirtier to extend
Step 1:
Create your own validator something like this:
<?php
namespace Your\Name\Space;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception;
use Symfony\Component\Validator\MetadataInterface;
use Symfony\Component\Validator\Validator\ContextualValidatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class myValidator implements ValidatorInterface
{
/**
* #var ValidatorInterface
*/
protected $validator;
/**
* #param ValidatorInterface $validator
*/
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
/**
* Returns the metadata for the given value.
*
* #param mixed $value Some value
*
* #return MetadataInterface The metadata for the value
*
* #throws Exception\NoSuchMetadataException If no metadata exists for the given value
*/
public function getMetadataFor($value)
{
return $this->validator->getMetadataFor($value);
}
/**
* Returns whether the class is able to return metadata for the given value.
*
* #param mixed $value Some value
*
* #return bool Whether metadata can be returned for that value
*/
public function hasMetadataFor($value)
{
return $this->validator->hasMetadataFor($value);
}
/**
* Validates a value against a constraint or a list of constraints.
*
* If no constraint is passed, the constraint
* {#link \Symfony\Component\Validator\Constraints\Valid} is assumed.
*
* #param mixed $value The value to validate
* #param Constraint|Constraint[] $constraints The constraint(s) to validate
* against
* #param array|null $groups The validation groups to
* validate. If none is given,
* "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validate($value, $constraints = null, $groups = null)
{
//the code you are doing all of this for
if (is_object($value) && $value instanceof PreValidateInterface) {
$value->preValidate();
}
//End of code
return $this->validator->validate($value, $constraints, $groups);
}
/**
* Validates a property of an object against the constraints specified
* for this property.
*
* #param object $object The object
* #param string $propertyName The name of the validated property
* #param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validateProperty($object, $propertyName, $groups = null)
{
$this->validator->validateProperty($object, $propertyName, $groups);
}
/**
* Validates a value against the constraints specified for an object's
* property.
*
* #param object|string $objectOrClass The object or its class name
* #param string $propertyName The name of the property
* #param mixed $value The value to validate against the
* property's constraints
* #param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
*
* #return ConstraintViolationListInterface A list of constraint violations.
* If the list is empty, validation
* succeeded
*/
public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null)
{
$this->validator->validatePropertyValue($objectOrClass, $propertyName, $value, $groups);
}
/**
* Starts a new validation context and returns a validator for that context.
*
* The returned validator collects all violations generated within its
* context. You can access these violations with the
* {#link ContextualValidatorInterface::getViolations()} method.
*
* #return ContextualValidatorInterface The validator for the new context
*/
public function startContext()
{
$this->validator->startContext();
}
/**
* Returns a validator in the given execution context.
*
* The returned validator adds all generated violations to the given
* context.
*
* #param ExecutionContextInterface $context The execution context
*
* #return ContextualValidatorInterface The validator for that context
*/
public function inContext(ExecutionContextInterface $context)
{
$this->validator->inContext($context);
}
}
Step 2:
Extend Symfony\Component\Validator\ValidatorBuilder something like this:
namespace Your\Name\Space;
use Symfony\Component\Validator\ValidatorBuilder;
class myValidatorBuilder extends ValidatorBuilder
{
public function getValidator()
{
$validator = parent::getValidator();
return new MyValidator($validator);
}
}
You need to override Symfony\Component\Validator\Validation. This is the ugly/dirty part, because this class is final so you can't extend it, and has no interface to implement, so you will have to pay attention to in on future versions of symfony in case backward compatibility is broken. It goes something like this:
namespace Your\Name\Space;
final class MyValidation
{
/**
* The Validator API provided by Symfony 2.4 and older.
*
* #deprecated use API_VERSION_2_5_BC instead.
*/
const API_VERSION_2_4 = 1;
/**
* The Validator API provided by Symfony 2.5 and newer.
*/
const API_VERSION_2_5 = 2;
/**
* The Validator API provided by Symfony 2.5 and newer with a backwards
* compatibility layer for 2.4 and older.
*/
const API_VERSION_2_5_BC = 3;
/**
* Creates a new validator.
*
* If you want to configure the validator, use
* {#link createValidatorBuilder()} instead.
*
* #return ValidatorInterface The new validator.
*/
public static function createValidator()
{
return self::createValidatorBuilder()->getValidator();
}
/**
* Creates a configurable builder for validator objects.
*
* #return ValidatorBuilderInterface The new builder.
*/
public static function createValidatorBuilder()
{
return new MyValidatorBuilder();
}
/**
* This class cannot be instantiated.
*/
private function __construct()
{
}
}
And last step overwrite the parameter validator.builder.factory.class in your config.yml:
parameters:
validator.builder.factory.class: Your\Name\Space\MyValidation
This is the least invasive way to do it, that i can find. Is not that clean and it could need some maintaining when you upgrade symfony to future versions.
Hope this helps, and happy coding
Alexandru Cosoi

How can I validate a "does not match" type using CodeIgniter's Form Validation class?

I want to ensure that a certain text field does not contain a specific value. Is there any way for me to do this using CI's Form Validation class or do I have to write my own extension for it?
I would extend the form validation class:
http://codeigniter.com/user_guide/general/creating_libraries.html
Something like
<?
class MY_Form_validation extends CI_Form_validation {
function __constuct() {
parent::__constuct();
}
function isnt($str,$field){
$this->CI->form_validation->set_message('isnt', "%s contains an invalid response");
return $str!==$field;
}
}
?>
Your validation rule would be something like
trim|alpha_numeric|isnt[invalid value]
Or, you can create a callback function instead of extending the class. The form validation section of the CI user guide has a relevant example:
http://codeigniter.com/user_guide/libraries/form_validation.html#callbacks
I agree with Billiam that you should extend the Form_validation class
I find it is more likely that one would want to validate a white-list of possible string values rather than a black-list. For example, you know your 'shirt_size' field should only return the string values: 'xl','l','m','s'. My solution is to handle both cases.
I use these methods in MY_From_validation:
/**
* ENUM
* The submitted string must match one of the values given
*
* usage:
* enum[value_1, value_2, value_n]
*
* example (any value beside exactly 'ASC' or 'DESC' are invalid):
* $rule['order_by'] = "required|enum[ASC,DESC]";
*
* example of case-insenstive enum using strtolower as validation rule
* $rule['favorite_corey'] = "required|strtolower|enum[feldman]";
*
* #access public
* #param string $str the input to validate
* #param string $val a comma separated lists of values
* #return bool
*/
function enum($str, $val='')
{
if (empty($val))
{
return FALSE;
}
$arr = explode(',', $val);
$array = array();
foreach($arr as $value)
{
$array[] = trim($value);
}
return (in_array(trim($str), $array)) ? TRUE : FALSE;
}
// --------------------------------------------------------------------
/**
* NOT ENUM
* The submitted string must NOT match one of the values given
*
* usage:
* enum[value_1, value_2, value_n]
*
* example (any input beside exactly 'feldman' or 'haim' are valid):
* $rule['favorite_corey'] = "required|not_enum['feldman','haim']";
*
* #access public
* #param string $str the input to validate
* #param string $val a comma separated lists of values
* #return bool
*/
function not_enum($str, $val='')
{
return ($this->enum($str,$val) === TRUE)? FALSE : TRUE;
}
Using Billiam's example, the validation rule not allow string 'invalid value' would be something like:
trim|alpha_numeric|not_enum[invalid value]
Actually, there's a fairly simple example given for this very question in the User Guide - for V2 or V3
Look for the section "Callbacks: Your own Validation Functions". In the example it uses a check for the word "test" in the username field, and returns the custom error if the value is found.
In your controller, change the "username" rule to this:
$this->form_validation->set_rules('username', 'Username', 'callback_username_check');
Then add a new function called username_check to your controller:
function username_check($str)
{
if ($str == 'test')
{
$this->form_validation->set_message('username_check', 'The %s field can not be the word "test"');
return FALSE;
}
else
{
return TRUE;
}
}
And Bob's your uncle...
CodeIgniter's form validation class can call almost any declared PHP function in your rule set. So I would simply declare a function like so:
class yourController {
function someFunction() {
$this->form_validation->set_rules('the_field_you_want_to_check', 'The Field Name', 'trim|myvalfunc[not this value]|xss');
}
}
function myvalfunc($formvalue, $notallowed) {
$this->CI->form_validation->set_message('myvalfunc', "%s is not allowed");
return $formvalue !== $nowallowed;
}

Resources