plz i have some rules function that i dont want to change in its code (BaseModel from database)
and i make other rules function in the model , i want to change in it
the base one "dont ant to change"
public function rules() {
return array(
array('start_date, end_date, info, type_id', 'required'),
array('start_date, end_date, type_id', 'length', 'max'=>10),
}
and i want to change in other function in the model that inherit from the base model
here is its code
public function rules()
{
$rules = array(
array('start_date, end_date, type_id', 'length', 'max'=>16),
);
return array_merge($rules,parent::rules());
}
the problem that it uses max value as 10 , not 16
i want it 16
even when i chenge last line to
return array_merge(parent::rules(),$rules);
The reason it might not work is because the rule for 'max'=>10 still exists in rules() irrespective of the order. Replacing the rule works for me.
public function rules(){
$rules=parent::rules();
$rules[1]=array('start_date, end_date, type_id', 'length', 'max'=>16);
return $rules;
}
Edit
To replace the rule using 'start_date, end_date, type_id' as the "key" you need a recursive array search:
public function rules(){
$rules=parent::rules();
$index=0;//index of the rule
outer: foreach($rules as $rule){
if(array_search('start_date, end_date, type_id',$rule)!==false)
break outer;
$index++;
}
$rules[$index]=array('start_date, end_date, type_id', 'length', 'max'=>16);
return $rules;
}
This checks for the existing rule and if it doesn't exist adds the rule as the last element of $rules. This won't work if someone edits the first element in the base class e.g to add a new property/field to the rule.
Just completed the same task, created a merging helper function:
public function mergeRules($parent, $rules)
{
// creating curent rules map
$rulesMap = [];
foreach ($rules as $rule) {
$validator = $rule[1];
$attributes = array_map('trim', explode(',', $rule[0]));
if (!isset($rulesMap[$validator])) {
$rulesMap[$validator] = [];
}
$rulesMap[$validator] = CMap::mergeArray($rulesMap[$validator], $attributes);
}
// checking for attributes in parent rules against created rule map
if (!empty($rulesMap)) {
foreach ($parent as $i => $rule) {
$validator = $rule[1];
if (empty($rulesMap[$validator])) {
continue;
}
$attributes = array_map('trim', explode(',', $rule[0]));
foreach ($attributes as $k => $attribute) {
if (in_array($attribute, $rulesMap[$validator])) {
unset($attributes[$k]);
}
}
// unset if we got inherited rule of same validator
if (empty($attributes)) {
unset($parent[$i]);
} else {
$parent[$i][0] = implode(', ', $attributes);
}
}
}
return array_merge($parent, $rules);
}
Then in rules() function you just call:
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
$rules = array(
blablabla...
);
return $this->mergeRules(parent::rules(), $rules);
}
One way to do this is to give your rules a key.
From the docs
Note: It is handy to give names to rules i.e.
public function rules()
{
return [
// ...
'password' => [['password'], 'string', 'max' => 60],
];
}
You can use it in a child model:
public function rules()
{
$rules = parent::rules();
unset($rules['password']);
return $rules;
}
In your case you could do this in your parent class:-
public function rules() {
return array(
'requiredArray' => array('start_date, end_date, info, type_id', 'required'),
'maxLengthArray' => array('start_date, end_date, type_id', 'length', 'max'=>10),
}
and in your child class:-
public function rules()
{
$parent = parent::rules();
$rules = array(
'maxLengthArray' => array('start_date, end_date, type_id', 'length', 'max'=>16)
);
unset($parent['maxLengthArray']);
return array_merge($parent,$rules);
}
Edit:
Keeping in mind that if you want to update only 'one' attribute within the 'maxLengthArray'... the others will have to be re-applied after 'unset' from parent ( obviously :) ).
eg:-
Changing only type_id length... the child would look like:-
public function rules()
{
$parent = parent::rules();
$rules = array(
'maxLengthArray' => array('start_date, end_date', 'length', 'max'=>10),
'typeIdLength' => array('type_id', 'length', 'max'=>16),
);
unset($parent['maxLengthArray']);
return array_merge($parent,$rules);
}
Related
I have model like below, where i have defined some static variables (which is not in DB table) then i am trying to fetch those variables but it returns those variables which is in DB table. I am trying to fetch both variables (static variables as well as variables which is in DB table).
Model
class Eforms extends CActiveRecord
{
public $emp_name;
public $current_status;
public $action_type;
public $action_type_extra;
public $common_value = array(
1 => 'Yes',
2 => 'No',
);
public $hr_only_value = array(
1 => 'IT',
2 => 'BOLD',
);
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return 'tbl_eforms';
}
public function rules()
{
return array(
array('form_id', 'required'),
array('form_id, user_id', 'numerical', 'integerOnly'=>true),
array('name_in_form', 'length', 'max'=>500),
array('pdf_name', 'length', 'max'=>1000),
array('emp_name, current_status, action_type, action_type_extra', 'required', 'on'=>'form1'),
array('emp_name, current_status, action_type, action_type_extra','safe'),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, form_id, user_id, name_in_form, email_recipients, pdf_name, created_on', 'safe', 'on'=>'search'),
);
}
................
...............
Controller :
public function actionIndex()
{
$model=new Eforms;
var_dump($model->attributes);exit;
}
If i changes CActiveRecord with CFormModel the it returns the only static variables not the DB related one.
From yii1 doc http://www.yiiframework.com/doc/api/1.1/CActiveRecord#attributes-detail
$model->attributes
Returns all column attribute values. Note, related objects are not
returned.
So you can access to the (related/calculated) var using
$myVar = $model->emp_name;
or
$model->emp_name = 'my_emp_name_value';
need help updating a unique rule in my validation rules. I have a abstract validator that will validate a rules before storing into my database and in the rules array I set the email to be unique when creating or registering a user but when updating the user the enique email should not validate if the email is owned by the user.
abstract class Validator
abstract class Validator {
protected $errors;
protected $attributes;
public function __construct($attributes = null)
{
$this->attributes = $attributes ?: \Input::all();
}
public function passes()
{
$validation = \Validator::make($this->attributes, $this->rules());
if ($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
public function getErrors()
{
return $this->errors;
}
}
Validation Rules(UserRule.php)
use MyCustomValidatorNamespaceHere....
class UserRules extends Validator
public function rules()
{
return [
'email' => 'required|email|unique:users,email,id',
...
];
}
and in my UserController I injected the UserRule in the constractor. (UserRule $userRule). Here is the code in the update method.
public function update($id)
{
$if ($this->userRule->passes())
{
$this->user->find($id)->update(Input::all());
return .........
}
}
But the validation always fail and displaying the error that the email is already taken. Please help guys.
The problem is your rule. When you update, you need to use unique that doesn't check record you update. So you should have:
unique:users,email,id
but for example:
unique:users,email,10
if you edit record with id 10.
What you could do is to define this rule:
'email' => 'required|email|unique:users,email,{id}',
and your passes method:
public function passes($id = null)
{
$rules = $this->rules();
$rules['email'] = str_replace('{id}', $id, $rules['email']);
$validation = \Validator::make($this->attributes, $rules);
if ($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
and now in update rule use:
if ($this->userRule->passes($id))
By the way you have error in $if ($this->userRule->passes()) - it should be if and not $if
You can use the route method inside your request class to except an id from the validation
public function rules()
{
return [
'email' => 'required|email|unique:users,email,'.$this->route('user'),
...
];
}
I had a problem like that before and it was difficult to find an answer. Here is what I did.
class UserRules extends Validator {
public function __construct($input = NULL) {
$this->input = $input ?: \Input::all();
if(isset($this->input['id'])):
self::$rules['username'] = 'required|unique:users,username,'.$this->input['id'];
self::$rules['email'] = 'required|email|unique:users,email,'.$this->input['id'];
else:
self::$rules['username'] = 'required|unique:users';
self::$rules['email'] = 'required|email|unique:users';
endif;
}
public static $rules = array(
'company_id' => 'required',
'role' => 'required',
'password' => 'sometimes|required|confirmed'
);
}
You need to use self:: because $rules is static.
I have moved this function:
public function passes($id)
{
$rules = static::$rules;
$rules['username'] = str_replace('{id}', $id, $rules['username']);
$rules['email'] = str_replace('{id}', $id, $rules['email']);
$validation = \Validator::make($this->attributes, $rules);
if($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
into UserRule.php and commented the same function in abstract class Validator
Now updating is working.
I solved this problem here on stackoverflow in a generic way. It will automatically adapt your rules if you do an update and add exceptions to your :unique if necessary.
in my Register Form, i want check email already in database:
class RegisterForm extends CFormModel
{
public $firstName;
public $lastName;
public $email;
public function rules()
{
return array(
array('firstName, lastName, email', 'required'),
array('email', 'email'),
array('email', 'checkEmail'),
);
}
public function attributeLabels()
{
return array();
}
public function checkEmail()
{
$record = Account::model()->findByAttributes(array('username'=>$this->email));
if ($record==null) {
return true;
}
return false;
}
}
but not working, how to create my rules for check email? somebody can help me????
Yii validators actually do not return true or false depending on validation result. Instead they add errors to your model, like this
public function checkEmail()
{
$record = Account::model()->findByAttributes(array('username'=>$this->email));
if ($record != null) {
$this->addError('email', 'Email should be unqiue');
}
}
Better use CUniqueValidator for this check
public function rules()
{
return array(
array('firstName, lastName, email', 'required'),
array('email', 'email'),
array('email', 'unique', 'attributeName' => 'username', 'className' => 'Account'),
);
}
That should work.
I would like to make a Yii Model rule dynamic, according to an attribute.
It's not giving error but also not working.
Am I doing something wrong? There any easier way to do it?
Model.php (Attributes: NAME, TYPE)
public function rules()
{
return array(
// Name is only required when Type is equal 1.
$this->type==1 ? array('name', 'required') : null,
);
}
Change to ( use scenario):
public function rules()
{
return array(
array('name', 'required','on'=>'typeTrue')
);
}
And in controller
public function actionSome() {
$model = new Model();
if ( $model->type == 1 ) {
$model->setScenario('typeTrue');
}
}
I found the answer and I would like to share.
For it need to use Yii rules scenarios.
Model.php:
public function rules()
{
return array(
array('name', 'required', 'on'=>'type1'),
);
}
Controller.php:
...
if ($model->type==1) {
$model->scenario = 'type1';
}
....
Let's say I have a product which can have a colour. Depending on the product type, the colour field may or may not be required.
If colour is always required, I would have the following in the product model
public function rules()
{
return array(
array('colour', 'required')
);
}
However, I want this to be dynamic depending on the product type.
Should this be done in the controller? I would imagine having something like the following in the controller:
public function actionOrder() {
// ....
if ($product->HasColour) {
// set the colour validation to be required
} else {
// set the colour validation to be not required
}
}
What is the best way to approach this?
Thanks
You can use scenario. In the model:
class Model extends CActiveRecord {
// ....
public function rules() {
return array(
array('colour', 'required', 'on' => 'hasColour')
);
}
// ....
}
And in the controller:
public function actionOrder() {
// ....
$model = new Product();
if ($product->HasColour) {
$model->setScenario('hasColour');
}
}
So, required colour will be validated when the model's scenario is hasColour
class LoginForm extends CFormModel
{
public $username;
public $password;
}
$form = new LoginForm();
$form->validatorList->add(
CValidator::createValidator('required', $form, 'username, password')
);
Now $form has two required fields.
One approach is to use a custom validation rule. For example, the rule:
array('colour', 'requiredOnHasColour'),
And then the validator method in the same model class:
public function requiredOnHasColour($attribute, $params) {
if ($this->hasColour && $this->$attribute == null)
$this->addError($attribute, 'Colour is required.');
}
More info: Create your own validation rule
If you want to do more complicated logic, then scenarios might not satisfy your needs. Then you can override method init and do all the logic that define validation rules over there, adding results to $validationRules array. And the in rules() method you just return that array. Something like that:
class Person extends CActiveRecord
{
public function init(){
if( TRUE){
$this->validationRules[] = array('first_name','required');
$this->validationRules[] = array('last_name','required');
}
}
public $validationRules = array(
array('email', 'required'),
array('email, email1, email2, email3', 'email', 'message'=>'Email format is invalid'),
array('email, address, email1, email2, email3', 'length', 'max'=>255),
);
public function rules()
{
return $this->validationRules;
}
}
In your ActiveRecord you can add in any place dynamic validator. For example i overwrite parent method setAttributes and add $this->validatorList->add() depends on selected value in $this->type attribute. Official docs: https://www.yiiframework.com/doc/api/1.1/CValidator
public function setAttributes($values, $safeOnly = true)
{
$result = parent::setAttributes($values, $safeOnly);
if (self::TYPE_PHONE == $this->type) {
$this->validatorList->add(CValidator::createValidator('required', $this, ['phone', 'phonePrefix']));
} elseif (self::TYPE_EMAIL == $this->type) {
$this->validatorList->add(CValidator::createValidator('required', $this, ['email']));
}
return $result;
}