Laravel-Aktualisierungsmodell mit eindeutiger Validierungsregel für Attribute


75

Ich habe ein Laravel- UserModell, das eine eindeutige Validierungsregel für usernameund hat email. Wenn ich in meinem Repository das Modell aktualisiere, validiere ich die Felder erneut, um kein Problem mit der erforderlichen Regelüberprüfung zu haben:

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

Dies schlägt beim Testen mit fehl

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

Gibt es eine Möglichkeit, dies elegant zu beheben?

Antworten:


141

Hängen Sie die idInstanz, die gerade aktualisiert wird, an den Validator an.

  1. Übergeben Sie die idIhrer Instanz, um den eindeutigen Validator zu ignorieren.

  2. Verwenden Sie im Validator einen Parameter, um festzustellen, ob Sie die Ressource aktualisieren oder erstellen .

Erzwingen Sie beim Aktualisieren, dass die eindeutige Regel eine bestimmte ID ignoriert:

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

Gehen Sie beim Erstellen wie gewohnt vor:

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

Bezieht sich die Benutzer-ID also auf die E-Mail-Adresse oder was?
Jonathan

OK. Angenommen, wenn Sie die bereits vorhandene E-Mail-Adresse aktualisieren, wie finden Sie diese dann?
Vinoth Kumar

Bitte schauen Sie diese Frage brauchen Hilfe stackoverflow.com/questions/39591620/…
Ayaz Shah

4
@ xcy7e 웃: laravel.com/docs/5.3/validation unique:table,column,except,idColumn
Luca Filosofi

funktioniert es nur für unique, bedeutet, dass ich es lteals verwendet habe, 'order'=> 'lte:products,stock,2'aber es funktioniert nicht warum?
Haritsinh Gohil

31

Ein weiterer eleganter Weg ...

Erstellen Sie in Ihrem Modell eine statische Funktion:

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

Validierung beim Erstellen:

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

Validierung beim Update:

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

Validierung beim Update mit einigen zusätzlichen Regeln:

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

Nett.


1
Sehr schön! Um meine auf diese Weise zum Arbeiten zu bringen, brauchte ich Folgendes:'email' => 'required|email|unique:member'. ($id ? ",id,$id" : '')
Nickspiel

10

Arbeiten innerhalb meiner Frage:

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

ist das, was ich getan habe, basierend auf der oben akzeptierten Antwort.

BEARBEITEN: Mit Formularanfragen wird alles einfacher:

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

Sie müssen nur die UpdateUserRequest an Ihre Aktualisierungsmethode übergeben und die Modell-ID POSTEN.


3
Wo postest du die ID?
Norgul

8

Einzigartige Validierung mit unterschiedlicher Spalten-ID in Laravel

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

Nur dieser hat für mich gearbeitet. Weil in der MongoDB-Primärspalte _idandere Antworten für mich nicht funktionierten.
AliN11

3

Laravel 5 kompatibler und generischer Weg:

Ich hatte nur das gleiche Problem und löste es auf generische Weise. Wenn Sie ein Element erstellen, werden die Standardregeln verwendet. Wenn Sie ein Element aktualisieren, werden Ihre Regeln überprüft :uniqueund automatisch ein Ausschluss eingefügt (falls erforderlich).

Erstellen Sie eine BaseModelKlasse und lassen Sie alle Ihre Modelle davon erben:

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

Sie definieren jetzt Ihre Regeln in Ihrem Modell so, wie Sie es gewohnt sind:

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

Und validieren Sie sie in Ihrem Controller. Wenn das Modell nicht validiert wird, wird es automatisch mit den entsprechenden Validierungsfehlern zum Formular zurückgeleitet. Wenn keine Validierungsfehler aufgetreten sind, wird der Code danach weiter ausgeführt.

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

Das ist es! :) Beachten Sie, dass wir beim Erstellen Role::getRules()und beim Bearbeiten aufrufen $role->getUpdateRules().


3

oder was Sie in Ihrer Formularanforderung tun könnten, ist (für 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
               ];
    }

Ich habe es in Laravel 5.6 gemacht und es hat funktioniert.


2

Ich habe die BaseModel-Klasse, also brauchte ich etwas allgemeineres.

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

Nehmen wir in der Benutzerklasse an, ich brauche nur E-Mail und Namen, um validiert zu werden:

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

Ich habe dies mit phpunit getestet und funktioniert einwandfrei.

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

}

Ich hoffe, dass ich jemandem helfen kann, auch wenn ich eine bessere Idee habe. Danke, dass du auch deine geteilt hast. (getestet auf Laravel 5.0)


2

Ein einfaches Beispiel für die Aktualisierung von Rollen


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

}

1

Ich rufe verschiedene Validierungsklassen für Store und Update auf. In meinem Fall möchte ich nicht alle Felder aktualisieren, daher habe ich baseRules für allgemeine Felder zum Erstellen und Bearbeiten. Fügen Sie jeweils zusätzliche Validierungsklassen hinzu. Ich hoffe mein Beispiel ist hilfreich. Ich benutze Laravel 4.

Modell:

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

Controller: Speichermethode:

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

Aktualisierungsmethode:

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

Ich hatte das gleiche Problem. Was ich getan habe: Fügen Sie in meiner Ansicht ein verstecktes Feld mit der ID eines Modells hinzu und überprüfen Sie im Validator das eindeutige Feld, nur wenn ich eine ID aus der Ansicht habe.

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

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

Es ist für Laravel 5.3 Version
Anuraag

1
Sie sollten Code in Antworten formatieren (das habe ich für Sie getan). Außerdem sollte eine Antwort im Allgemeinen nicht nur Code ohne Anmerkungen sein. Möglicherweise möchten Sie eine kurze Erklärung hinzufügen, warum dieser bestimmte Code für die Situation hilfreich ist.
Dan Lowe

1

Sie können Code unten versuchen

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

Können Sie uns etwas mehr erklären?
Dieter Meemken

1

Wenn Sie eine andere Spalte haben, die als Fremdschlüssel oder Index verwendet wird, müssen Sie dies auch in der Regel wie dieser angeben.

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

1

Laravel 5.8 einfach und leicht

Sie können dies alles in einer Formularanfrage mit ganz nett tun. . .

Erstellen Sie zunächst ein Feld, in dem Sie die ID (unsichtbar) in der normalen Bearbeitungsform übergeben können. dh

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

... Fügen Sie dann die Regelklasse wie folgt zu Ihrer Formularanforderung hinzu:

use Illuminate\Validation\Rule;

... Fügen Sie die eindeutige Regel hinzu und ignorieren Sie die aktuelle ID wie folgt:

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

    ];

... Geben Sie abschließend den Hinweis auf die Formularanforderung in die Aktualisierungsmethode genauso ein wie auf die Speichermethode:

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


}

Auf diese Weise wiederholen Sie den Code nicht unnötig :-)


1
public function rules()
{
    if ($this->method() == 'PUT') {
        $post_id = $this->segment(3);
        $rules = [
            'post_title' => 'required|unique:posts,post_title,' . $post_id
        ];
    } else {
        $rules = [
            'post_title' => 'required|unique:posts,post_title'
        ];
    }
    return $rules;
}

0

Für eine benutzerdefinierte FormRequest und Laravel 5.7+ können Sie die ID Ihres aktualisierten Modells wie folgt abrufen:

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

0

Für alle, die eine Formularanfrage verwenden

In meinem Fall habe ich alle folgenden Versuche unternommen: Keiner von ihnen hat funktioniert:

$this->id, $this->user->id, $this->user.

Es war, weil ich weder auf das Modell $idnoch $iddirekt zugreifen konnte .

Also habe ich das $idvon einer Abfrage erhalten, die dasselbe uniqueFeld verwendet, das ich zu validieren versuche:

    /**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    $id = YourModel::where('unique_field',$this->request->get('unique_field'))->value('id');
    return [
        'unique_field' => ['rule1','rule2',Rule::unique('yourTable')->ignore($id)],
    ];
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.