Conditional Validation in Yii2 - validation

I have a radio button with two values (required field) based on that value one field is shown (there are two fields which are intially hidden, its shown based on value of radio button) which should be required. So I used conditional validation for the initially hidden fields.
This is my model code:
public function rules()
{
return [
[['receipt_no', 'date_of_payment', 'payment_method_id',
'total_amount'], 'required'],
['nonmember_name', 'required', 'whenClient' => function($model)
{
return $model->is_member == 2;
}, 'enableClientValidation' => false],
['member_id', 'required', 'whenClient' => function($model)
{
return $model->is_member == 1;
}, 'enableClientValidation' => false],
[['receipt_no', 'date_of_payment', 'payment_method_id',
'total_amount','is_member'], 'required','on' => 'receipt'],
];
}
I use a scenario receipt, is_member is the radio button field. If I select a value of 1 for is_member then field member_id is visible and it should be a required one. If is_member has a value 2 then nonmember_name is displayed and it should become a required field. With my code in model I managed to achieve it. But now other actions (saving a new row of data into model) using this model is having error
Array ( [nonmember_name] => Array ( [0] => Name cannot be blank. ) )
So my question is how can I make conditional validation specific to a scenario (I think that my error is due to required rule defined in conditional validation )
EDIT:
This is my radio button
<?= $form->field($model, 'is_member')->radioList(array('1'=>'Member',2=>'Non Member'))->label('Member or Not'); ?>

In rules
public function rules()
{
return [
[
'nonmember_name',
'required',
'when' => function ($model) {
return $model->is_member == 2;
},
'whenClient' => "function (attribute, value) {
return $('#id').val() == '2';
}"
]
];
}

I prefer to use functions inside the model's rules, which makes it a lot easier to work with in the future.
One thing to mention that a lot of answers don't mention, is that you MUST manually re-trigger the Yii2 client side validation!
$("#w0").yiiActiveForm("validateAttribute", "createuserform-trainer_id");
In my example below, there are 2 types of accounts: trainer and trainee. In my admin panel, the admin can create a new user. If they choose "trainer" there is nothing more to do. If they choose a "trainee", that "trainee" must be assigned a "trainer" to go under.
So in code terms:
If user role == trainee, trainer_id is required and show it's form input.
Else hide the trainer_id input and trainer_id will not be required.
Model rules:
public function rules()
{
return [
[
'trainer_id', 'required', 'when' => function ($model) {
return $model->role == 2;
}, 'whenClient' => "isUserTypeTraineeChosen"
],
];
}
View after your form:
<?php $this->registerJs('
function isUserTypeTraineeChosen (attribute, value) {
if ($("#createuserform-role").val() == 2) {
$(".field-createuserform-trainer_id").show();
} else {
$("#createuserform-trainer_id").val(null);
$(".field-createuserform-trainer_id").hide();
}
return ($("#createuserform-role").val() == 2) ? true : false;
};
jQuery( "#createuserform-role" ).change(function() {
$("#w0").yiiActiveForm("validateAttribute", "createuserform-trainer_id");
});
jQuery( "#createuserform-role" ).keyup(function() {
$("#w0").yiiActiveForm("validateAttribute", "createuserform-trainer_id");
});
'); ?>
Note: This is a dropdown list, so change and keyup both are necessary to accurately detect it's change statuses. If your not using a dropdown, then both may not be necessary.
I also have targeted the trainer_id input to be hidden by default using CSS, as the default user type is a trainer.

Related

Hidden input value does not update model property value yii2

Im using activeform. In my User model, i initialized a model property Public formType;, set its rule to safe and i am trying to use this property with hiddeninput to create condition in the user controller. But i am getting that the activeform doesn't update the value of the property. Ive read this but i am still unclear whats the workaround of updating the property while still using activeform.
Form
<?= $form->field($model, 'formType')->hiddenInput(['value' => 'userRowUpdate'])->label(false) ?>
User Controller
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post())) {
$model->scannedFile = \yii\web\UploadedFile::getInstance($model, 'scannedFile');
$type = Yii::$app->request->post('formType');
if ($model->processAndSave()) {
FlashHandler::success("User profile updated success!");
if ($type == "userDetailView") {
return $this->redirect(['view', 'id' => $model->id]);
} else if ($type == "userRowUpdate") {
return $this->redirect(Yii::$app->request->referrer);
}
} else {
FlashHandler::err("User profile updated FAIL!");
}
}
return $this->render('update', [
'model' => $model,
]);
}
Replace the hiddeninput from using activeform to
<?=Html::hiddenInput('name', $value);?>
Reasons:-
code in question is probably not the right approach since its creating a attribute/model property just for the condition in action controller
html input wouldnt affect the model but the data is still readable and be used in the actioncontroller as a condition
You can use
Html::activeHiddenInput($model, 'formType')
to avoid hiding the label and it's shorter.
if the value is not being updated, check your rules in the model. That attribute at least must have a safe rule to be able to be assigned on the $model->load(Yii::$pp->request->post()) call

Yii2 Validate that a table record entry does not exist in another table

I am developing a system in yii2 that validates that a national_ID entry does not belong to a dead person. So I have a table registration1 and deathregister table. I wanted that when I make an entry in the registration1 it should validate that the Id number entered DOES NOT EXIST in table deathregister.
Currently I have this code which is only validating that the record exists, the question I wanted help is to customize it to check if record does NOT exist.
['identitynumber', 'exist', 'targetClass' => Deathregister::className(), 'targetAttribute' => ['nationalidnum' => 'idnum'],'message'=>'The ID number Supplied Belongs to the Dead'],
As Bizley suggest, you should do it manually. I think what you need is something like this. You can run a custom validator which will add something to errors or not (based on empty errors array it considers validity)
public function rules()
{
return [
['identitynumber', 'notExistsValidator'],
];
}
public function notExistsValidator()
{
if(!Deathregister::findOne(['nationalidnum' => $this->identitynumber])->exists())
{
$this->addError('nationalidnum', 'The ID number Supplied Belongs to the Dead');
}
}
As 'exist' is used for availability, the 'unique' validator checks to see if the input value in a table column is unique.
As before targetClass, targetAttribute, filter is valid for unique.
Note: It only works with Active Record model attributes
But you can also use the following methods:
Inline Validators:
return [
['identitynumber', 'unique',
'targetClass' => Deathregister::className(),
'targetAttribute' => ['nationalidnum' => 'idnum'],
'message'=>'The ID number Supplied Belongs to the Dead'
/*Also, if validation of one attribute depends on the value of another attribute */
// 'when' => function ($model) {
// if (condition) {
// return $this->addError('identitynumber', Yii::t('user', 'The ID number Supplied Belongs to the Dead'));
// }
// return false;
// }
],
];
Example2:
return [
// validatorMethod()
['identitynumber', 'validatorMethod'],
// or
['identitynumber', function ($attribute, $params) {
if(!Deathregister::findOne(['nationalidnum' => $this->$attribute])->exists()) {
$this->addError($attribute, 'The ID number Supplied Belongs to the Dead.');
}
}],
];
// validatorMethod()
public function ValidatorMethod($attribute, $params)
{
if(!Deathregister::findOne(['nationalidnum' => $this->$attribute])->exists()) {
$this->addError($attribute, 'The ID number Supplied Belongs to the Dead');
}
}
/** Or */
// public function validatorMethod()
// {
// if(!Deathregister::findOne(['nationalidnum' => $this->identitynumber])->exists())
# code ...
// }
// }
You can also extend the class (Standalone Validators)
It is better to refer to this page and this page.
Link2
Note:If you want to make sure a rule is always applied, you may configure the skipOnEmpty and/or skipOnError properties to be false in the rule declarations.

add Symfony Assert in a Callback

I, i have to add an Assert to an atribute when other atribute is equal than something. Like this:
/**
* #Assert\Callback(methods={"isChildMinor",)
*/
class PatientData
{
/**
* #Assert\Date()
*/
public $birthday;
public $role;
public function isChildMinor(ExecutionContext $context)
{
if ($this->role == 3 && check #assert\isMinor() to $birtday) {
=>add violation
}
}
so, i want check if the patient is minor (with assert or somethings else) if the role is equal than 3. How do this?
There are several ways to do, what you want.
1) You could make it right in the form. Like that:
use Symfony\Component\Validator\Constraints as Assert;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$yourEntity = $builder->getData();
//here you start the field, you want to validate
$fieldOptions = [
'label' => 'Field Name',
'required' => true,
];
if ($yourEntity->getYourProperty != 'bla-bla-bla') {
$fieldOptions[] = 'constraints' => [
new Assert\NotBlank([
'message' => 'This is unforgivable! Fill the field with "bla-bla-bla" right now!',
]),
],
}
$builder->add('myField', TextType::class, $fieldOptions);
2) Other way - is to make your custom validation callback in your Entity and play with direct asserts there. It's possible, I think.
3) But the optimal way, from my point of view - is to use several asserts with validation groups. You need to specify Assert\isMinor(groups={"myCustomGroup"}) on birthday field. And then, in your form:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'validation_groups' => function (FormInterface $form) {
$yourEntity = $form->getData();
if ($yourEntity->role !== 3) {
return ['Default', 'myCustomGroup'];
}
return ['Default'];
},
Hope this'll be helpful for you.

How to reinitialize model when client side validation fails in Yii 2?

I am working on Yii 2 form and I want to reinitialize model when client side validation fails. For example with certain rules like below:
public function rules()
{
return [
[['username'], 'required', 'message' => 'You must enter your username'],
['username','email'],
[['password'], 'required', 'message' => 'You must enter your password'],
];
}
When validation fails I want all fields to be empty (for example when user enters invalid email address). How can I do that?
I assume you use standard Yii 2 way of loading the model:
$model = new SomeModel();
if ($model->load(\Yii::$app->request->post()) && $model->save()) {
// ...
}
return $this->render('view', ['model' => $model]);
Set fields to null when validation fails. You don't want to create new instance (which would be easier) because you would lost all validation messages.
$model = new SomeModel();
if ($model->load(\Yii::$app->request->post())) {
if ($model->save()) {
// ....
} else {
$model->username = null;
$model->password = null;
}
}
return $this->render('view', ['model' => $model]);
UPDATE: for the client side validation add this JS code in view:
$("#form-ID").on("afterValidateAttribute", function (event, attribute, messages) {
if (event.result === false) {
attribute.value = "";
}
});
Replace #form-ID with proper form element JS identifier.

yii2 custom validation not working

I need to compare 2 attribute value in the model and only if first value is lower than second value form can validate.I try with below code but it not worked.
controller
public function actionOpanningBalance(){
$model = new Bill();
if ($model->load(Yii::$app->request->post())) {
$model->created_at = \Yii::$app->user->identity->id;
$model->save();
}else{
return $this->render('OpanningBalance', [
'model' => $model,
]);
}
}
Model
public function rules()
{
return [
[['outlet_id', 'sr_id', 'bill_number', 'bill_date', 'created_at', 'created_date','bill_amount','credit_amount'], 'required'],
[['outlet_id', 'sr_id', 'created_at', 'updated_at'], 'integer'],
[['bill_date', 'd_slip_date', 'cheque_date', 'created_date', 'updated_date','status'], 'safe'],
[['bill_amount', 'cash_amount', 'cheque_amount', 'credit_amount'], 'number'],
[['comment'], 'string'],
['credit_amount',function compareValue($attribute,$param){
if($this->$attribute > $this->bill_amount){
$this->addError($attribute, 'Credit amount should less than Bill amount');
}],
[['bill_number', 'd_slip_no', 'bank', 'branch'], 'string', 'max' => 225],
[['cheque_number'], 'string', 'max' => 100],
[['bill_number'], 'unique']
];
}
}
It's going in to the validator function but not add the error like i wanted
$this->addError($attribute, 'Credit amount should less than Bill amount');
anyone can help me with this?
If the validation is not adding any error, it's most likely being skipped. The issue is most likely becasue of default rules behaviour whereby it skips empty or already error given values as per here: https://www.yiiframework.com/doc/guide/2.0/en/input-validation#inline-validators
Specifically:
By default, inline validators will not be applied if their associated attributes receive empty inputs or if they have already failed some validation rules. If you want to make sure a rule is always applied, you may configure the skipOnEmpty and/or skipOnError properties to be false in the rule declarations.
So you would need to set up the skipOnEmpty or skipOnError values depending on what works for you:
[
['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false],
]
Try this:
public function actionOpanningBalance(){
$model = new Bill();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
$model->created_at = \Yii::$app->user->identity->id;
$model->save();
}else{
return $this->render('OpanningBalance', [
'model' => $model,
]);
}
}
For Validation
You can use anonymous function :
['credit_amount',function ($attribute, $params) {
if ($this->$attribute > $this->bill_amount)) {
$this->addError($attribute, 'Credit amount should less than Bill amount.');
return false;
}
}],
you can use like this below answer is also write
public function rules(){
return [
['credit_amount','custom_function_validation', 'on' =>'scenario'];
}
public function custom_function_validation($attribute){
// add custom validation
if ($this->$attribute < $this->cash_amount)
$this->addError($attribute,'Credit amount should less than Bill amount.');
}
I've made custom_function_validation working using 3rd params like this:
public function is18yo($attribute, $params, $validator)
{
$dobDate = new DateTime($this->$attribute);
$now = new DateTime();
if ($now->diff($dobDate)->y < 18) {
$validator->addError($this, $attribute, 'At least 18 years old');
return false;
}
}
This is a back end validation and it will trigger on submit only. So you can try something like this inside your validation function.
if (!$this->hasErrors()) {
// Your validation code goes here.
}
If you check the basic Yii2 app generated you can see that example in file models/LoginForm.php, there is a function named validatePassword.
Validation will trigger only after submitting the form.

Resources