Validation rule for updating unique field

I have a livewire component that updates a model in the database. Name and icon must be unique so previously in Laravel I pass in the ID of the model in the validation rule.

This still works if I put the rules inside the $this->validate(); method but not when I define the validation $rules property, since it needs to be a constant property.

But I need my validation to be inside the $rules property because I’m using model binding! Also its much clearer to define them this way.

But how do I write the rules to work when updating unique values? Or do I need to write a custom validation method to achieve this?

class MarkEdit extends Component
{
    public Mark $mark;

    // Dosent work
    protected $rules = [
        'mark.name' => 'required|string|min:2|unique:marks,name,' . $this->mark->id,
        'mark.icon' => 'required|string|unique:marks,icon,' . $this->mark->id,
        'mark.tooltip' => 'nullable|string|max:512',
        'mark.color' => 'nullable|string',
    ];

// etc..
}

Hey, @Awayn
Are there any errors in this way while updating the fields?

Cant load any page when the $rule is defined with a variable:

Symfony\Component\ErrorHandler\Error\FatalError
Constant expression contains invalid operations

Have you tried to dd the $mark property -mark object-?

There´s noting wrong with the data, and it doesn’t matter anyway since its a syntax error in the $rule property. It must be corrected first before any code will run at all.

You can use a rules() method that returns the validation array instead of the protected $rules property. So something like this should work:

function rules() {
    return [
        'mark.name' => 'required|string|min:2|unique:marks,name,' . $this->mark->id,
        'mark.icon' => 'required|string|unique:marks,icon,' . $this->mark->id,
        'mark.tooltip' => 'nullable|string|max:512',
        'mark.color' => 'nullable|string',
    ];
}
3 Likes

I tried that and then i get this error instead:

Error
Typed property App\Http\Livewire\Admin\MarkEdit::$mark must not be accessed before initialization

I tried with a isset() etc. but from what i can tell it looks like livewire is reading the rules first, before the route model binding takes effect.

How are the rest of you handling updates on unique fields? Shouldn’t it be a pretty trivial task.

So far I’ve only used unique validation rules with the logged in user where I’ve used auth()->user() instead of accessing the model on $this so I don’t have experience using it with the route model binding. That said what happens if you wrap the calls to $this->mark in a call to optional() like this:

function rules() {
    return [
        'mark.name' => 'required|string|min:2|unique:marks,name,' . optional($this->mark)->id,
        'mark.icon' => 'required|string|unique:marks,icon,' . optional($this->mark)->id,
        'mark.tooltip' => 'nullable|string|max:512',
        'mark.color' => 'nullable|string',
    ];
}

@Awayn did you ever solve your problem. I’m facing a similar one.

Not in a optimal way, but for now I’m declaring the rules property with empty strings. That way the model binding still works!

public Mark $mark;

protected $rules = [
    'mark.name' => '',
    'mark.icon' => '',
    'mark.tooltip' => '',
    'mark.color' => '',
];

And then I inject the actual rules directly into the validate method.

public function save()
{
    $this->validate([
        'mark.name' => 'required|string|min:2|unique:marks,name,' . $this->mark->id,
        'mark.icon' => 'required|string|unique:marks,icon,' . $this->mark->id,
        'mark.tooltip' => 'nullable|string|max:512',
        'mark.color' => 'nullable|string',
    ]);

    $this->mark->save();

    return redirect()->route('admin.mark.index');
}

@Awayn did you find better solution? i have a same issue :frowning:

And you don’t bind that incoming data in the mount method? This issue is because you have to:

public $mark;

public function mount(MarkEdit $mark)
{
$this->mark = $mark;
}
Prove this anyway?

I discovered you can simplify it a bit further with:

class Foo extends Component
{
    protected array $rules = [];

    public function mount()
    {
        $this->rules = $this->rules();
    }
    
    public function rules()
    {
         return [
             'unique_field' => 'required|unique:table,column,' . $this->model_id;
         ];
    }

    public function save()
    {
         $this->validate();

         // Do you your thing
     }

In my case, I’m not using Model Route Binding at this time, but I’m sure the above could be adapted depending on how the component is used.