yaeykay's avatar

Laravel 4 Unique Validation Rules Not Working

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 {

    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;
    }
}
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.

0 likes
11 replies
FrankPeters's avatar

According to the unique documentation you need to pass the ID as a number into the third parameter:

'email' => 'unique:users,email,10'

So you need to find a way to pass through the $id to the UserRules class.

yaeykay's avatar

Yeah I am thinking about that hmmm about 3 hours ago. Until now I don't have a solution.

mourabraz's avatar

I am sorry! I new in this thing, but why you do not pass de ID as a parameter in you function rules()?

RhythmScout's avatar

I came across this issue as well. Here is what I arrived at (partially borrowed from http://stackoverflow.com/questions/22405762/laravel-update-model-with-unique-validation-rule-for-attribute)

Rules defined in my model:

public static $rules = [
    'create' => [
        'first_name' => 'required|min:1|max:128',
        'last_name' => 'required|min:1|max:128',
        'email' => 'required|email|max:128|unique:users',
        'username' => 'required|max:24|unique:users',
    ],
    'update' => [
        'first_name' => 'required|min:1|max:128',
        'last_name' => 'required|min:1|max:128',
        'email' => 'required|email|max:128|unique:users,email,:id',
    ]
];

public static function rules( $action, $merge=[], $id=false)
{
    $rules = SELF::$rules[$action];

    if ($id) {
        foreach ($rules as &$rule) {
            $rule = str_replace(':id', $id, $rule);
        }
    }

    return array_merge( $rules, $merge );
}

This seems to do the trick for me and allows me to neatly have update and create rules in a single place. Can also dynamically add rules when called:

$staff_validation = Validator::make($input, Staff::rules('update', [// add addl rules], $id));

if ($staff_validation->fails()) {
    $this->errors->merge($staff_validation->messages());
}

I'm fairly new to Laravel, is there anything obviously wrong with this method? Am I violating any principles here?

cgrossde's avatar

If you want a generic solution without automatic adaption of your rules for an update check here or keep on reading:

Laravel 5 compatible, 100% automated way:

I just had the same problem and solved it in a more general way that works 100% automatic. It adapts the rules automatically for an update and also takes into account if a :unique-field did change or not.

Create a BaseModel class and let all your models inherit from it:

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model {

    /**
     * The validation rules for this model
     *
     * @var array
     */
    protected static $rules = [];

    /**
     * Return model validation rules
     *
     * @return array
     */
    public static function getRules() {
        return static::$rules;
    }

    /**
     * Return model validation rules for an update
     * Add exception to :unique validations where necessary
     * That means: enforce unique if a unique field changed.
     * But relax unique if a unique field did not change
     *
     * @return array;
     */
    public function getUpdateRules() {
        $updateRules = [];
        foreach(self::getRules() as $field => $rule) {
            $newRule = [];
            // Split rule up into parts
            $ruleParts = explode('|',$rule);
            // Check each part for unique
            foreach($ruleParts as $part) {
                if(strpos($part,'unique:') === 0) {
                    // Check if field was unchanged
                    if ( ! $this->isDirty($field)) {
                        // Field did not change, make exception for this model
                        $part = $part . ',' . $field . ',' . $this->getAttribute($field) . ',' . $field;
                    }
                }
                // All other go directly back to the newRule Array
                $newRule[] = $part;
            }
            // Add newRule to updateRules
            $updateRules[$field] = join('|', $newRule);

        }
        return $updateRules;
    }
}    

You now define your rules in your model like you are used to:

protected static $rules = [
    'name' => 'required|alpha|unique:roles',
    'displayName' => 'required|alpha_dash',
    'permissions' => 'array',
];

And validate them in your Controller. If the model does not validate, it will automatically redirect back to the form with the corresponding validation errors. If no validation errors occurred it will continue to execute the code after it.

public function postCreate(Request $request)
{
    // Validate
    $this->validate($request, Role::getRules());
    // Validation successful -> create role
    Role::create($request->all());
    return redirect()->route('admin.role.index');
}

public function postEdit(Request $request, Role $role)
{
    // Validate
    $this->validate($request, $role->getUpdateRules());
    // Validation successful -> update role
    $role->update($request->input());
    return redirect()->route('admin.role.index');
}

That's it! :) Note that on creation we call Role::getRules() and on edit we call $role->getUpdateRules().

yosmany's avatar

I just had the same issue and decided to create an account to share my solution with you. All I did was to recreate the rules in the Validator function when a user id wasn't empty, meaning it's an update.

private $rules = array(
    'email' => 'required|email|unique:users,email',
    'password' => 'required|alphaNum|min:6|same:cpassword',
    'firstname' => 'required',
    'lastname' => 'required'
);

public function validate($data){
    if (!empty($data['user_id'])){
        $rules = array(
            'email' => 'required|email|unique:users,email,'.$data['user_id'].',user_id',
            'firstname' => 'required',
            'lastname' => 'required'
        );
    } else {
        $rules = $this->rules;
    }
    // Make a new validator object
    $validator = Validator::make($data, $rules, $this->messages);
    // Check for failure
    if ($validator->fails()){
        // Set errors and return false
        $this->errors = $validator->errors();
        return false;
    }
    // Validation passed
    return true;
}

In my controller I just call:

if ($user->validate(Input::all())){
    //code here...
}

I hope it helps. :-)

OmarMakled's avatar

I use

<?php namespace Acme\Validation;

use Illuminate\Validation\Factory as Validator;

abstract class FormValidator {

    protected $validator;

    protected $validation;

    function __construct(Validator $validator)
    {
        $this->validator = $validator;
    }

    public function validate (array $formData,$id = null)
    {
        $this->validation = $this->validator->make($formData, $this->getValidationRules($id));
        
        if ($this->validation->fails())
        {
            throw new FormValidationException ('Validation failed',$this->getValidationErrors());
        }

        return true;
    }


    protected function getValidationRules($id)
    {
        if ($id)
        {
            array_walk($this->rules, function (&$v, $k) use($id){
                $v = str_replace(':id', $id, $v); 
            }); 
        }
        return $this->rules;
    }

    protected function getValidationErrors()
    {
        return $this->validation->errors();
    }
}
use Acme\Validation\FormValidator;

class ProfileFormValidator extends FormValidator {

/**
 * Validation rules
 *
 * @var array
 */
public $rules = [
    'username'              => 'required|unique:users,username,:id',
    'email'                 => 'email|unique:users,email,:id',
    'password'              => 'sometimes|confirmed|required|min:6',
];

}

$this->form->validate($formData,$id);

1 like
eduardoarandah's avatar

THIS IS AN EASY SOLUTION

Just add $this->route('id') as the third parameter

if your route was defined like this:

Route::put('{company}', 'CompanyController@update')
        ->name('update');

then your parameter name is "company"

So in your FormRequest:

public function rules()
{
    $rules = [
        'url' => [
            'required',
            'url',
            'unique:companies,url,'.$this->route('company') ?? 0
        ],    
    ];
    // dd($rules); << check yourself

    return $rules;
}

This FormRequest works the same for insert or update.

this line will instruct to ignore "0" in case of insert

$this->route('company') ?? 0

Please or to participate in this conversation.