web-dev-qa-db-fra.com

Modèle de mise à jour Laravel avec règle de validation unique pour l'attribut

J'ai un modèle laravel User qui a une règle de validation unique sur username et email. Dans mon référentiel, lors de la mise à jour du modèle, je revalide les champs afin de ne pas poser de problème de validation des règles:

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

Cela échoue dans les tests avec 

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

Y a-t-il un moyen de régler cela élégamment?

51
Tom Macdonald

Ajoutez la id de l'instance en cours de mise à jour au validateur.

  1. Passez la id de votre instance pour ignorer le validateur unique.

  2. Dans le validateur, utilisez un paramètre pour détecter si vous utilisez update ou crée la ressource.

En cas de mise à jour, forcez la règle unique à ignorer un identifiant donné:

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

Si vous créez, procédez comme d'habitude:

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

Une autre façon élégante ...

Dans votre modèle, créez une fonction statique:

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

Validation sur create:

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

Validation sur mise à jour:

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

Validation à la mise à jour, avec quelques règles supplémentaires:

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

Agréable.

27
BaM

Travailler dans le cadre de ma question:

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

est ce que j'ai fait, basé sur la réponse acceptée ci-dessus.

EDIT: Avec les demandes de formulaire, tout est simplifié:

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

Il vous suffit de transmettre UpdateUserRequest à votre méthode de mise à jour et de vous assurer de POST l'identifiant du modèle.

8
Tom Macdonald

Laravel 5 compatible et générique:

Je viens d'avoir le même problème et résolu d'une manière générique. Si vous créez un élément, il utilise les règles par défaut. Si vous le mettez à jour, il vérifie la présence de :unique dans vos règles et insère automatiquement une exclusion (si nécessaire).

Créez une classe BaseModel et laissez tous vos modèles en hériter:

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

Vous définissez maintenant vos règles dans votre modèle comme vous êtes habitué à:

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

Et validez-les dans votre contrôleur. Si le modèle ne valide pas, il sera automatiquement redirigé vers le formulaire avec les erreurs de validation correspondantes. Si aucune erreur de validation ne s'est produite, il continuera à exécuter le code après celui-ci.

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

C'est tout! :) Notez que lors de la création, nous appelons Role::getRules() et lors de la modification, nous appelons $role->getUpdateRules().

3
cgross

Validation unique avec différents identifiants de colonne dans Laravel

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

Un exemple simple pour la mise à jour des rôles


// 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
Ricardo Canelas

J'ai la classe BaseModel, donc j'avais besoin de quelque chose de plus générique.

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

Dans la classe utilisateur, supposons que je n'ai besoin que de l'email et du nom pour être validé:

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

J'ai testé cela avec phpunit et fonctionne bien. 

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

}

J'espère que ça va aider quelqu'un, même si pour avoir une meilleure idée. Merci de partager le vôtre aussi . (Testé sur Laravel 5.0)

2
Angel M.

J'appelle différentes classes de validation pour Store et Update. Dans mon cas, je ne veux pas mettre à jour tous les champs. J'ai donc baseRules pour les champs communs pour créer et éditer. Ajoutez des classes de validation supplémentaires pour chacun. J'espère que mon exemple est utile. J'utilise Laravel 4.

Modèle: 

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

Contrôleur: Méthode de stockage:

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

Méthode de mise à jour:

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
Oat

ou ce que vous pourriez faire dans votre demande de formulaire est (pour 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
               ];
    }

je l'ai fait dans Laravel 5.6 et cela a fonctionné.

1
DaShInG Sid

Vous pouvez essayer le code ci-dessous

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',
];
1
luongit
'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
tanmay

Si vous avez une autre colonne utilisée comme clé étrangère ou index, vous devez l'indiquer également dans la règle.

'phone' => [
                "required",
                "phone",
                Rule::unique('shops')->ignore($shopId, 'id')->where(function ($query) {
                    $query->where('user_id', Auth::id());
                }),
            ],
1
Chaudhry Waqas
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
Binal Patel

J'ai eu le même problème… .. Ce que j'ai fait: ajouter dans ma vue un champ masqué avec l'identifiant d'un modèle et dans le validateur vérifier l'unique, uniquement si j'ai un identifiant de vue.

$this->validate(
        $request,
        [
            'index'       => implode('|', ['required', $request->input('id') ? '' : 'unique:members']),
            'name'        => 'required',
            'surname'     => 'required',
        ]
);
1
svolkov

Pour un FormRequest personnalisé et Laravel 5.7+, vous pouvez obtenir l'ID de votre modèle mis à jour comme suit:

public function rules()
    {
        return [
            'name' => 'required|min:5|max:255|unique:schools,name,'.\Request::instance()->id
        ];
    }
0
steve

Laravel 5.8 simple et facile

vous pouvez faire tout cela dans une demande de formulaire avec assez bien. . .

commencez par créer un champ par lequel vous pouvez passer l'identifiant (invisible) dans le formulaire d'édition normal. c'est à dire.,

 <div class="form-group d-none">
      <input class="form-control" name="id" type="text" value="{{ $example->id }}" >
 </div>

... Ensuite, veillez à ajouter la classe Rule à votre demande de formulaire de la manière suivante:

use Illuminate\Validation\Rule;

... Ajoutez la règle Unique en ignorant l'id actuel, comme ceci:

public function rules()
{
    return [
          'example_field_1'  => ['required', Rule::unique('example_table')->ignore($this->id)],
          'example_field_2'  => 'required',

    ];

... Enfin, tapez que la demande de formulaire dans la méthode de mise à jour est identique à celle utilisée dans la méthode store, comme suit:

 public function update(ExampleValidation $request, Examle $example)
{
    $example->example_field_1 = $request->example_field_1;
    ...
    $example->save();

    $message = "The aircraft was successully updated";


    return  back()->with('status', $message);


}

De cette façon, vous ne répéterez pas le code inutilement :-)

0
Rick