skip to Main Content

I have a main model and it has a field rules:

class User extends yiidbActiveRecord
{
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return 'users';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['username', 'email'], 'required'],
        ];
    }
}

I must say right away that I cannot make changes to this model, I can make all changes only in the next model that will be inherited from this:

class UserForm extends User
{
    public function rules(): array
    {
        return array_merge(
            parent::rules(),
            [ 
                ['username', 'safe', 'skipOnEmpty' => true],
            ]
        );
    }
}

The bottom line is that in the first model the username field is required, but now I need to make it so that when filling out the form it is empty, and when saving the value of the email field is copied to the username field.

For copying I use the function:

public function beforeSave($insert)
    {
        if (empty($this->username)) {
            $this->username = $this->email;
        }

        return parent::beforeSave($insert);
    }

But due to the fact that the username field is required, nothing is saved for me, what I did in the child model does not work. How can I make a field optional without editing the first model?

2

Answers


  1. You could bypass the required validation by adding a where clause in your rule:

    [['username', 'email'], 'required', 'when' => function() { return false; }],
    

    Since the function always returns false, the validator is never aplied.

    Login or Signup to reply.
  2. You can’t replace the rule by adding another. If you define rules like that, the result is that both, required and safe rule are applied for username field.

    You have to find the required rule in rules definition array and remove username from attributes the rule applies to.
    It can be easy if you know that the rule definition will always be in specific position. For example if it’s always the first rule in parent model:

    public function rules()
    {
        $rules = parent::rules();
        // the array diff is used to remove only 'username' field from list of attributes
        $rules[0][0] = array_diff($rules[0][0], ['username']);        
        return $rules;
    }
    

    If you don’t know the position of required rule, you will need to use some cycle to find it. For example like this:

    foreach ($rules as $rule) {
        if (
            $rule[1] == 'required'
            && (
                (is_array($rule[0]) && in_array('username', $rule[0]))
                || (is_string($rule[0]) && $rule[0] == 'username')
            )
        ) {
           // rule found
        }  
    }
    

    But there is another possible solution to your problem. Instead of copying the value of email field in beforeSave() callback you can copy it in beforeValidate() callback. That way when the username field is validated it will already have the value of email field and it will pass validation.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search