使用唯一验证规则更新Laravel模型属性

100

我有一个 Laravel 的 User 模型,其中的 usernameemail 有独特的验证规则。在我的Repository中,当我更新模型时,我会重新验证这些字段,以避免出现必填规则验证问题:

public function update($id, $data) {
    $user = $this->findById($id);
    $user->fill($data);
    $this->validate($user->toArray());
    $user->save();

    return $user;
}

以下是测试失败的错误信息:

ValidationException: {"username":["该用户名已被使用。"],"email":["该邮箱已被使用。"]}

是否有一种优雅的方法来解决这个问题?

20个回答

182

将当前正在更新的实例的id附加到验证器中。

  1. 将您的实例的id传递给忽略唯一性验证器。

  2. 在验证器中,使用参数检测您是在更新还是创建资源。

如果正在进行更新,则强制唯一规则忽略给定的ID:

//rules
'email' => 'unique:users,email_address,' . $userId,

如果正在创建,请按照惯例进行:

//rules
'email' => 'unique:users,email_address',

那么用户ID与电子邮件地址有关系吗? - Jonathan
好的。假设您正在更新已存在的电子邮件地址,那么如何找到它呢? - Vinoth Kumar
7
这是一个来自 laravel.com 的文档链接,介绍了 Laravel 5.3 版本中关于数据验证的方法 unique。在该方法中,可以使用参数 tablecolumnexceptidColumn 来进行更细致的验证操作。 - Luca Filosofi
这个唯一性检查不适用于任何全局作用域,我不得不添加一个检查 Rule::unique('table', 'name')->ignore($userId)->whereNull('deleted_at') 来防止它针对已删除的记录进行检查。 - adrianp
1
这对我来说是最好的解决方案,谢谢!! - Jenuel Ganawed
显示剩余4条评论

39

另一种优雅的方法是...

在你的模型中创建一个静态函数:

public static function rules ($id=0, $merge=[]) {
    return array_merge(
        [
            'username'  => 'required|min:3|max:12|unique:users,username' . ($id ? ",$id" : ''),
            'email'     => 'required|email|unique:member'. ($id ? ",id,$id" : ''),
            'firstname' => 'required|min:2',
            'lastname'  => 'required|min:2',
            ...
        ], 
        $merge);
}

创建时的验证:

$validator = Validator::make($input, User::rules());

更新时的验证:

$validator = Validator::make($input, User::rules($id));

更新时的验证,附加了一些规则:

$extend_rules = [
    'password'       => 'required|min:6|same:password_again',
    'password_again' => 'required'
];
$validator = Validator::make($input, User::rules($id, $extend_rules));

不错。


2
非常好!为了让我的代码以这种方式工作,我需要以下内容: 'email' => 'required|email|unique:member'. ($id ? ",id,$id" : '') - nickspiel

11

在我的问题范围内工作:

public function update($id, $data) {
    $user = $this->findById($id);
    $user->fill($data);
    $this->validate($user->toArray(), $id);
    $user->save();
    return $user;
}


public function validate($data, $id=null) {
    $rules = User::$rules;
    if ($id !== null) {
        $rules['username'] .= ",$id";
        $rules['email'] .= ",$id";
    }
    $validation = Validator::make($data, $rules);
    if ($validation->fails()) {
        throw new ValidationException($validation);
    }
    return true;
}

基于上面得到的被接受答案,这就是我所做的。

编辑:使用表单请求,一切变得更简单:

<?php namespace App\Http\Requests;

class UpdateUserRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|unique:users,username,'.$this->id,
            'email' => 'required|unique:users,email,'.$this->id,
        ];
    }
}

你只需要将UpdateUserRequest传递给你的更新方法,并确保POST模型ID。


4
你要在哪里发布身份证号码? - Norgul

10

或者你可以在你的表单请求中这样做(适用于Laravel 5.3+)

public function rules()
{
    return [ 
        'email' => 'required|email|unique:users,email,'. $this->user
         //here user is users/{user} from resource's route url
    ];
}

我已经在Laravel 5.6中完成了它,并且它可以工作。


这是最简单和最容易的答案。 - Emtiaz Zahid

10

Laravel中使用不同列ID进行唯一验证

'UserEmail'=>"required|email|unique:users,UserEmail,$userID,UserID"

只有这个对我起作用了。因为在MongoDB中,主列是_id,其他答案对我没用。 - AliN11

4
'email' => [
    'required',
    Rule::exists('staff')->where(function ($query) {
        $query->where('account_id', 1);
    }),
],

'email' => [
    'required',
    Rule::unique('users')->ignore($user->id)->where(function ($query) {
        $query->where('account_id', 1);
    })
],

这是针对 Laravel 5.3 版本的内容。 - Tanmay
2
你应该在回答中格式化代码(我已经为您完成了)。此外,一般来说,答案不应只是没有注释的代码 - 您可能需要添加一个简短的解释,说明为什么这个特定的代码对于这种情况有帮助。 - Dan Lowe

3

Laravel 5 兼容且通用的方法:

我曾经遇到过同样的问题,并以一种通用的方式解决了它。如果您创建一个项目,它将使用默认规则;如果您更新一个项目,它将检查您的规则是否包含 :unique 并自动插入一个排除项(如果需要)。

创建一个 BaseModel 类,并让所有的模型都继承它:

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

你现在可以像往常一样在你的模型中定义规则:

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

在控制器中验证它们。如果模型未通过验证,则会自动重定向回表单,并显示相应的验证错误。如果没有发生验证错误,它将继续执行其后的代码。
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');
}

就是这样! :) 注意,在创建时我们调用 Role::getRules(),在编辑时我们调用 $role->getUpdateRules()


2

一个简单的角色更新示例


// model/User.php
class User extends Eloquent
{

    public static function rolesUpdate($id)
    {
        return array(
            'username'              => 'required|alpha_dash|unique:users,username,' . $id,
            'email'                 => 'required|email|unique:users,email,'. $id,
            'password'              => 'between:4,11',
        );
    }
}       

.

// controllers/UsersControllers.php
class UsersController extends Controller
{

    public function update($id)
    {
        $user = User::find($id);
        $validation = Validator::make($input, User::rolesUpdate($user->id));

        if ($validation->passes())
        {
            $user->update($input);

            return Redirect::route('admin.user.show', $id);
        }

        return Redirect::route('admin.user.edit', $id)->withInput()->withErrors($validation);
    }

}

2
我有一个BaseModel类,所以我需要更通用的东西。
//app/BaseModel.php
public function rules()
{
    return $rules = [];
}
public function isValid($id = '')
{

    $validation = Validator::make($this->attributes, $this->rules($id));

    if($validation->passes()) return true;
    $this->errors = $validation->messages();
    return false;
}

在用户类中,假设我只需要验证电子邮件和姓名:
//app/User.php
//User extends BaseModel
public function rules($id = '')
{
    $rules = [
                'name' => 'required|min:3',
                'email' => 'required|email|unique:users,email',
                'password' => 'required|alpha_num|between:6,12',
                'password_confirmation' => 'same:password|required|alpha_num|between:6,12',
            ];
    if(!empty($id))
    {
        $rules['email'].= ",$id";
        unset($rules['password']);
        unset($rules['password_confirmation']);
    }

    return $rules;
}

我使用phpunit测试了这个功能,结果很好。

//tests/models/UserTest.php 
public function testUpdateExistingUser()
{
    $user = User::find(1);
    $result = $user->id;
    $this->assertEquals(true, $result);
    $user->name = 'test update';
    $user->email = 'ddd@test.si';
    $user->save();

    $this->assertTrue($user->isValid($user->id), 'Expected to pass');

}

我希望这能对某些人有所帮助,即使只是为了更好地理解。也感谢你分享你的经验。

(在Laravel 5.0上测试通过)


2
如果您有另一个被用作外键或索引的列,那么您也必须在规则中指定它,像这样:

如果您有另一个被用作外键或索引的列,那么您也必须在规则中指定它,像这样。

'phone' => [
                "required",
                "phone",
                Rule::unique('shops')->ignore($shopId, 'id')->where(function ($query) {
                    $query->where('user_id', Auth::id());
                }),
            ],

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接