unique validation is not working in Update case in YII? - php

I have applied unique validation for Employee Code. it is working fine for Add Emp case. But i am not getting why it is not working in case of Update, here is the code i have used for this:
My rule method is :
public function rules()
{
return array(
array('is_active', 'numerical', 'integerOnly'=>true),
array('first_name, last_name, employee_code, username, password, role,joining_date,', 'required','on'=>array('create','update')),
array('employee_code', 'numerical', 'integerOnly'=>true),
array('employee_code', 'unique','on'=>array('create','update')),
array('employee_code', 'length', 'min' => 4, 'max'=>4, 'message'=>Yii::t("translation", "{attribute} is too short.")),
array('username','email'),
array('username','valid_username','on'=>array('create')),
array('username', 'required','on'=>array('forgotPassword')),
array('currentPassword, newPassword, newPasswordRepeat', 'required','on'=>array('change')),
array('newPassword', 'compare', 'compareAttribute'=>'newPasswordRepeat','on'=>array('change'),'message'=>'New and Confirm Password do not match.'),
array('verifyCode', 'captcha', 'allowEmpty'=>!CCaptcha::checkRequirements(),'on'=>array('forgotPassword')),
array('joining_date', 'safe'),
array('years', 'safe'),
array('user_id, first_name, last_name, employee_code, username, password, role, joining_date, pending_regular_leave, pending_medical_leave, allocated_regular_leave, allocated_medical_leave, is_active', 'safe', 'on'=>'search'),
);
Please can someone identify where i am wrong. Please help me.

Looks like that the value of field "employee code" puts in form field and you are trying to save it. And it is not unique, and it is not save.
Anyway, you can:
$employee->validate(array('/* Here is list of attributes, except employee code */'));
or:
$employee->save(array('/* Here is list of attributes, except employee code */'));
in update action.
You can also delete scenario in validation rule:
array('employee_code', 'unique','on'=>array('insert');

When you update record it already exists in the database and the value you are going to validate already exists too. This is why when you do update you should exclude the record you are going to update from the check. Else the check will fail with the message like this
Employee Code "some value" has already been taken.
For example (new rule with criteria)
array(
'employee_code',
'unique',
'criteria' => array(
'condition' => 'user_id != :id',
'params' => array(':id' => $this->user_id),
),
'on' => array('create', 'update'),
),
I assume that user_id is the primary key.
Formally you might want to have different scenarios for update and create because you do not need any additional checks for the create scenario. Single scenario works as well but you might want to do something like this
array(
'employee_code',
'unique',
'on' => array('create'),
),
array(
'employee_code',
'unique',
'criteria' => array(
'condition' => 'user_id != :id',
'params' => array(':id' => $this->user_id),
),
'on' => array('update'),
),
P.S. As mentioned in one of the comments you might want to use insert instead of create because by default the scenario for newly created model is insert. This is more commonly used name.

Related

Laravel 5 Unique form validation ignore id / slug

These are my rules in my class:
class AppointmentsController extends Controller
{
protected $rules = [
'appointment' => ['required', 'min:5'],
'slug' => ['required', 'unique:appointments'],
'description' => ['required'],
'date' => ['required', 'date_format:"Y-m-d H:i"'],
];
This is in the laravel official docs:
Sometimes, you may wish to ignore a given ID during the unique check.
For example, consider an "update profile" screen that includes the
user's name, e-mail address, and location. Of course, you will want to
verify that the e-mail address is unique. However, if the user only
changes the name field and not the e-mail field, you do not want a
validation error to be thrown because the user is already the owner of
the e-mail address. You only want to throw a validation error if the
user provides an e-mail address that is already used by a different
user. To tell the unique rule to ignore the user's ID, you may pass
the ID as the third parameter:
'email' => 'unique:users,email_address,'.$user->id.',user_id'
I tried using this in my rules:
'slug' => ['required', 'unique:appointments,id,:id'],
This indeed ignores the current row BUT it ignores it completely. What I want to accomplish is, I want it to ignore the current row only if the slug is unchanged. When it is changed to something that is already unique in another row, I want it to throw an error.
The Unique validator works like that
unique:table,column,except,idColumn
So in your case, you can do it like that:
Get the id you want to validate against, you can get it from the route or with any other way that works for you; something like that
$id = $this->route('id');
'slug' => ['required','unique:appointments,slug,'.$id],
For example we need to update contact info into Users table.
In my model User I created this static method:
static function getContactDataValidationRules( $idUserToExcept ) {
return [
'email' => 'required|email|max:255|unique:users,email,' . $idUserToExcept,
'pec' => 'required|email|max:255',
'phone' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'mobile' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'phone2' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'recovery_email' => 'required|email|max:255',
];
}
and in my UsersController, into the method that update User I've:
$id = $request->input('id');
$request->validate(User::getContactDataValidationRules( $id ));
:-)

Laravel Validation one of two fields must be filled

I have two fields:
QQ
Email
How do I set up a Validator object so that one of these fields must be filled? It doesn't matter which.
$messages = array(
'email.required_without:qq' => Lang::get('messages.mustenteremail'),
'email.email' => Lang::get('messages.emailinvalid'),
'qq.required_without:email' => Lang::get('messages.mustenterqq'),
);
required_without should work.
It means that the field is required if the other field is not present. If have more than two fields and only one is required, use required_without_all:foo,bar,...
$rules = array(
'Email' => 'required_without:QQ',
'QQ' => 'required_without:Email',
);
In the example above (given by lucasgeiter) you actually only need one of the conditions not both.
$rules = array(
'Email' => 'required_without:QQ'
);
If you have both rules then filling neither in will result in TWO error messages being displayed. This way it will check that at least one field is filled in and will only display one error message if neither are filled.
Laravel >= 8.32 only support.
Both (mobile or email) are presented simultaneously -> throw an error.
Both (mobile or email) is not present simultaneously -> throw an error.
Allowed
Only one parameter can be allowed.
'email' => [ 'prohibited_unless:mobile,null,','required_without:mobile','email', 'max:255', ],
'mobile' => [ 'prohibited_unless:email,null','required_without:email', 'digits_between:5,13', 'numeric' ],
EDIT: You can also use prohibits:email and prohibits:mobile in combination of required_without:... rule

laravel 4.1 unique validation on update fails

Laravel 4.1. I want to update a city, check the rules and it fails on unique check.
Rules:
public static $rules = [
'name' => 'required|alpha_dash|unique:cities',
'slug' => 'alpha_dash|unique:cities',
'seo_title' => 'required|max:60|unique:cities',
'seo_description' => 'required|max:160|unique:cities',
'rank' => 'integer',
'visible' => 'integer'
];
I know, I can smth like:
'name' => 'required|alpha_dash|unique:cities, name, ##',
where ## - id, but I cant dynamically set id to updated one.
'name' => "required|alpha_dash|unique:cities, name, $id", // doesnt work
'name' => "required|alpha_dash|unique:cities, name, $this->id", // doesnt work
Is there any way to do it normally ?
You can do it in separate ways.
An easy way is to use different rules based on different actions . When you will create the model, you will use the rules that you currently have.
When you will update the model, you will change the unique:cities to exists:cities
I usually do this with a validation service.
You create a base abstract Validator in services/ , which has a passes() function.
For each model, you create a ModelValidator , in your case CityValidator. Where you put your rules like :
public static $rules = [
'new'=>[
'name' => 'required|alpha_dash|unique:cities',
'slug' => 'alpha_dash|unique:cities',
'seo_title' => 'required|max:60|unique:cities',
'seo_description' => 'required|max:160|unique:cities',
'rank' => 'integer',
'visible' => 'integer'],
'edit'=>[
'name' => 'required|alpha_dash|exists:cities',
'slug' => 'alpha_dash|unique:cities',
'seo_title' => 'required|max:60|exists:cities',
'seo_description' => 'required|max:160|exists:cities',
'rank' => 'integer',
'visible' => 'integer'
]
];
The 3rd argument accepts a value to be ignored... If you want to do a WHERE clause, do it like:
'name' => array("required", "alpha_dash", "unique:cities,name,null,id,id,$this->id"...
The docs says:
Adding Additional Where Clauses
You may also specify more conditions that will be added as "where"
clauses to the query:
'email' => 'unique:users,email_address,NULL,id,account_id,1'
In the rule above, only rows with an account_id of 1 would be included in the unique check.
Learn by example:
email => unique:users,email_address,id,NULL,field_1,value_1,field_2,value_2,field_x,value_x
Generates the query:
SELECT
count(*) AS AGGREGATE
FROM
`entries`
WHERE
`email_address` = ?
AND `id` <> NULL
AND `field_1` = `value_1`
AND `field_2` = `value_2`
AND `field_x` = `value_x`
I found an elegant-ish way to do this using fadion/ValidatorAssistant:
<?php
use Fadion\ValidatorAssistant\ValidatorAssistant;
class CityValidator extends ValidatorAssistant {
// standard rules
public static $rules = [
'name' => 'required|alpha_dash',
'slug' => 'alpha_dash',
'seo_title' => 'required|max:60',
'seo_description' => 'required|max:160',
];
// some preparation before validation
protected function before()
{
// Inject the given city id into the unique rules
$this->rules['name'] .= 'unique:cities,name,' . $this->inputs['id'];
$this->rules['slug'] .= 'unique:cities,slug,' . $this->inputs['id'];
$this->rules['seo_title'] .= 'unique:cities,seo_title,' . $this->inputs['id'];
$this->rules['seo_description'] .= 'unique:cities,seo_description,' . $this->inputs['id'];
}
There's almost certainly a more elegant way to do this when you need several fields to be unique database-wide, but the above works very well for times when you only need one part to be unique.

CDateValidator set null when empty

I'm going crazy trying out different solutions on the net of a fairly simple problem
I have literally hundreds of dates being stored by my application, and my client's source data has a bad habit of dates being in different formats.
For example, one of my models has rules as followed (for completion all are listed for this specific model)
public function rules() {
return array(
array('function, junior, ces,agreement_expected,start_date', 'required'),
array('start_budget', 'numerical'),
array('visa_received,training_days', 'numerical', 'integerOnly' => true),
array('function, junior, ces,currency', 'length', 'max' => 10),
array('agreement_received, status, stop_date_original, stop_date_extended', 'safe'),
array('agreement_received, visa_received, stop_date_original, stop_date_extended', 'default', 'setOnEmpty' => true, 'value' => null),
array('agreement_received,agreement_expected,start_date,stop_date_original,stop_date_extended', 'date', 'format' => 'Y-m-d', 'allowEmpty' => true),
array('id, Sname,PFces, PFdomain,PDkeyword, PFstatus, PRname,PRcountry,PRscore,PRcomment,PRngo,TRname,TRpic, TFfunction, TFx, TRdateofbirth,TRedufields,TRcoach,TRlocation,TRtask,TRcontract,TRproject,TRcontact,TFdateofbirth,TFedufields,TFcoach,TFlocation,TFtask,TFcontract,TFproject,TFcontact,
date1,date2,idate, ddomains,dkeywords,country,agreement, function,ngo, status,group, junior, junior_lastname, junior_firstname,search_all,search_interrupt, ces, agreement_expected, agreement_received, visa_received, start_date, stop_date_original, stop_date_extended,currency, start_budget, training_days', 'safe', 'on' => 'search'),
);
}
what I want to achieve is depicted by following rules
array('agreement_received, visa_received, stop_date_original, stop_date_extended', 'default', 'setOnEmpty' => true, 'value' => null),
array('agreement_received,agreement_expected,start_date,stop_date_original,stop_date_extended', 'date', 'format' => 'Y-m-d', 'allowEmpty' => true),
When I have a form, and I submit an empty value on say stop_date_extended, it is not set to NULL rather an empty string..
What am I doing wrong?
Surely there must be an easy work-around as not using the date validator works peachy.
Yii Default Validator class handles the empty values like this :
/framework/validators/CDefaultValueValidator.php
protected function validateAttribute($object,$attribute)
{
if(!$this->setOnEmpty)
$object->$attribute=$this->value;
else
{
$value=$object->$attribute;
if($value===null || $value==='')
$object->$attribute=$this->value;
}
}
Given the function above your only issue could be that the value[i.e $value] posting by form could be not empty string probably it might have spaces. Probably you could try trim() the values before assigning them to model attributes. Or extend the default value validator e.g
class CMyDefaultValueValidator extends CDefaultValueValidator
{
protected function validateAttribute($object,$attribute)
{
if(!$this->setOnEmpty)
$object->$attribute=$this->value;
else
{
$value=trim($object->$attribute);
if($value===null || $value==='')
$object->$attribute=$this->value;
}
}
}
just note the line : $value=trim($object->$attribute);
Although,I haven't tested above code, I do not know if this is could be done some better way,
if you want to insert null for empty strings, you can do it in your model, after validation:
public function afterValidate()
{
if(empty(trim($this->yourField))) // put whatever logic that you need
$this->yourField = null;
return parent::afterValidation();
}

Change validation rules on the fly

I'm working on a form that contains user data, specifically a phone number field. The phone number typically isn't required so the only validation rule in the model is the usphone rule. However, if the user is submitting this form, the phone number becomes necessary. I thought I'd be able to simply add a validate rule on the fly, set the model and call the validates method, but either I'm doing it wrong or it's not working the way I expected.
In my controller:
# Update a few validation rules that are specific to this context
$this->Proposal->Requestor->validate['phone_number']['notempty'] = array(
'rule' => 'notEmpty',
'message' => 'Please enter a phone number so can can contact you with any questions about the work.',
'allowEmpty' => false,
'required' => true,
);
$validationErrors = array();
$this->Proposal->Requestor->set( $this->data['Requestor'] ); # $this->data['Requestor']['phone_number'] only (no other requestor data)
if( !$this->Proposal->Requestor->validates( array( 'fieldList' => array( 'phone_number' ) ) ) ) {
$validationErrors['Requestor'] = $this->Proposal->Requestor->validationErrors;
}
No errors are reported, even if I leave the phone number field empty. In this case, the only information I'm requesting from the user is their phone number, so the rest of the Requestor data is empty, but I've tried merging in the rest of the user data and I get the same result. If I remove the fieldList option, I get an error on a different field, but still nothing on the empty phone number.
Any idea what I'm missing here? I've been monkeying around with this for hours now and I just haven't found the right answer.
Thanks.
The solution ended up being twofold:
I had existing rule on the phone_number field that forced the value to be a US phone number. That rule also set allowEmpty to true and required to false. I wanted to catch an empty value so I could display a particularly precise message.
I had to update the existing rule to flip the allowEmpty and required values and also add a new rule with its last value set to true.
The final change, added in my controller action looks like this:
$this->Proposal->Requestor->validate = Set::merge(
$this->Proposal->Requestor->validate,
array(
'phone_number' => array(
'notempty' => array(
'rule' => 'notEmpty',
'message' => 'Please enter a phone number so can can contact you with any questions about the work.',
'allowEmpty' => false,
'required' => true,
'last' => true,
),
'usphone' => array(
'allowEmpty' => false,
'required' => true,
),
)
)
);
I can't remember whether I verified that the change to the existing usphone rule was strictly necessary given the last value of the new rule, but this combination is working fine.
You could try using the Multivalidatable Behaviour - http://bakery.cakephp.org/articles/dardosordi/2008/07/29/multivalidatablebehavior-using-many-validation-rulesets-per-model

Categories