Модель обновления Laravel с уникальным правилом проверки для атрибута


У меня есть модель laravel User, которая имеет уникальное правило проверки для username и email. В моем репозитории, когда я обновляю модель, я повторно проверяю поля, чтобы не возникало проблем с обязательной проверкой правил:

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

Это не удается при тестировании с помощью

ValidationException: {"username":["The username has already been taken."],"email":["The email has already been taken."]}

Есть ли способ элегантно исправить это?

Author: marcanuy, 2014-03-14

14 answers

Добавьте id экземпляра, который в настоящее время обновляется, в средство проверки.

  1. Передайте id вашего экземпляра, чтобы игнорировать уникальный валидатор.

  2. В средстве проверки используйте параметр, чтобы определить, обновляете ли вы или создаете ресурс.

При обновлении заставьте уникальное правило игнорировать данный идентификатор:

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

При создании действуйте как обычно:

//rules
'email' => 'unique:users,email_address',
 102
Author: marcanuy, 2017-10-13 07:57:25

Еще один элегантный способ...

В вашей модели создайте статическую функцию:

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));

Мило.

 27
Author: BaM, 2014-10-16 13:38:16

Работа в рамках моего вопроса:

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 вашему методу обновления и обязательно ОПУБЛИКОВАТЬ идентификатор модели.

 7
Author: Tom Macdonald, 2016-01-23 14:54:39

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().

 3
Author: cgross, 2015-03-09 17:04:57

У меня есть класс базовой модели, поэтому мне нужно было что-то более универсальное.

//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 = '[email protected]';
    $user->save();

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

}

Я надеюсь, что это поможет кому-то, даже если для получения лучшей идеи. Спасибо, что поделились и своими. (протестировано на Laravel 5.0)

 2
Author: Angel M., 2015-01-22 07:58:54

Простой пример обновления ролей


// 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
Author: Ricardo Canelas, 2015-03-06 19:16:33

Уникальная Проверка С Другим Идентификатором Столбца В Laravel

'UserEmail'=>"required|email|unique:users,UserEmail,$userID,UserID"
 2
Author: user5797691, 2016-01-16 08:47:18

Я вызываю различные классы проверки для хранения и обновления. В моем случае я не хочу обновлять все поля, поэтому у меня есть базовые правила для общих полей для создания и редактирования. Добавьте дополнительные классы проверки для каждого из них. Я надеюсь, что мой пример будет полезен. Я использую Laravel 4.

Модель:

public static $baseRules = array(
    'first_name' => 'required',
    'last_name'  => 'required',
    'description' => 'required',
    'description2' => 'required',
    'phone'  => 'required | numeric',
    'video_link'  => 'required | url',
    'video_title'  => 'required | max:87',
    'video_description'  => 'required',
    'sex' => 'in:M,F,B',
    'title'  => 'required'
);

public static function validate($data)
{
    $createRule = static::$baseRules;
    $createRule['email'] = 'required | email | unique:musicians';
    $createRule['band'] = 'required | unique:musicians';
    $createRule['style'] = 'required';
    $createRule['instrument'] = 'required';
    $createRule['myFile'] = 'required | image';

    return Validator::make($data, $createRule);
}

public static function validateUpdate($data, $id)
{
    $updateRule = static::$baseRules;
    $updateRule['email'] = 'required | email | unique:musicians,email,' . $id;
    $updateRule['band'] = 'required | unique:musicians,band,' . $id;
    return Validator::make($data, $updateRule);
}

Контроллер: Способ хранения:

public function store()
{
    $myInput = Input::all();
    $validation = Musician::validate($myInput);
    if($validation->fails())
    {
        $key = "errorMusician";
        return Redirect::to('musician/create')
        ->withErrors($validation, 'musicain')
        ->withInput();
    }
}

Метод обновления:

public function update($id) 
{
    $myInput = Input::all();
    $validation = Musician::validateUpdate($myInput, $id);
    if($validation->fails())
    {
        $key = "error";
        $message = $validation->messages();
        return Redirect::to('musician/' . $id)
        ->withErrors($validation, 'musicain')
        ->withInput();
    }
}
 1
Author: Oat, 2015-02-04 02:06:56
public static function custom_validation()
{
    $rules = array('title' => 'required ','description'  => 'required','status' => 'required',);
    $messages = array('title.required' => 'The Title must be required','status.required' => 'The Status must be required','description.required' => 'The Description must be required',);
    $validation = Validator::make(Input::all(), $rules, $messages);
    return $validation;
}
 1
Author: Binal Patel, 2015-07-07 14:49:44

У меня была та же проблема. Что я сделал: добавьте в мое представление скрытое поле с идентификатором модели и в валидаторе проверьте уникальность, только если я получил какой-то идентификатор из представления.

$this->validate(
        $request,
        [
            'index'       => implode('|', ['required', $request->input('id') ? '' : 'unique:members']),
            'name'        => 'required',
            'surname'     => 'required',
        ]
);
 1
Author: svolkov, 2016-05-17 15:41:28
'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);
    })
],
 1
Author: tanmay, 2016-12-14 19:23:00

Вы можете попробовать код ниже

return [
    'email' => 'required|email|max:255|unique:users,email,' .$this->get('id'),
    'username' => 'required|alpha_dash|max:50|unique:users,username,'.$this->get('id'),
    'password' => 'required|min:6',
    'confirm-password' => 'required|same:password',
];
 0
Author: luongit, 2017-01-17 08:52:43

Если у вас есть другой столбец, который используется в качестве внешнего ключа или индекса, вы также должны указать это в правиле, подобном этому.

'phone' => [
                "required",
                "phone",
                Rule::unique('shops')->ignore($shopId, 'id')->where(function ($query) {
                    $query->where('user_id', Auth::id());
                }),
            ],
 0
Author: Chaudhry Waqas, 2017-05-12 22:39:50

Или то, что вы могли бы сделать в своем запросе формы (для 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, и это сработало.

 0
Author: DaShInG Sid, 2018-06-28 05:55:44