Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

oscaribarra25's avatar

Livewire component unique validation on related model

Hi,

I am a bit lost trying to implement a unique rule on a related model inside a Livewire component.

I have this livewire component called CreateEditContact with a form for contact fields plus two dynamic tables one for contact emails and the other for contact phones. These two tables have the options to add and remove dynamically in the form. This livewire component also have the following rules

'contact_emails.*.email_address' => 'required|email|max:50|unique:contact_emails,email_address'
'contact_phones.*.phone_number' => 'required|string|max:12|unique:contact_phones,phone_number'

Both of them work fine when creating a new contact but when I am trying to edit the unique validation is telling the email and phone already exists because it is already in the database for the model that is being edited. I know the unique rule has a third parameter to set the id of the record to be ignored. But not sure how to get this id for a related model inside the rule definition.

The database tables are according to this migrations (I removed the extra fields from this to shorten this post)

Contacts table

Schema::create('contacts', function (Blueprint $table) {
	$table->id();
});

Contact emails table

Schema::create('contact_emails', function (Blueprint $table) {
	$table->id();
	$table->unsignedBigInteger('contact_id');
	$table->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade')->onUpdate('cascade');
	$table->string('email_address');
});

Contact phones table

Schema::create('contact_phones', function (Blueprint $table) {
	$table->id();
	$table->unsignedBigInteger('contact_id');
	$table->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade')->onUpdate('cascade');
	$table->string('phone_number');
});

Can anyone tell me how can I fix this validation rule to successfully ignore the current record if editing.

Thanks in advance.

Best regards

0 likes
8 replies
oscaribarra25's avatar

Thanks for your feedback @valentin_vranic,

Yes I know the third parameter is responsible of telling what id to ignore. But now sure how to get it for a related model. For the main model I usually something like

'name' => 'required|unique:contacts,name,'.$this->contact->id

but that will bring me the contact id which a field of the main model, what I need is the id for the relation model something like

'contact_emails.*.email_address' => 'unique:contact_emails,email_address,'.??????????

But not sure how to get the correct id here. I thought on something like

$this->contact_emails[???]->id

But for that I would need an index I guess.

Thanks in advance

valentin_vranic's avatar
Level 5

@oscaribarra25 You're on a right track. To get the id of the relation, you're supposed to create a relation first in the model.

Let say in contacts model define HasMany relation

public function contactEmails(): HasMany
{
    return $this->hasMany(ContactEmails::class, 'contact_id');
}

and/or vice-versa for ContactEmails with HasOne

public function contact(): HasOne
{
    return $this->hasOne(Contact::class, 'id', 'contact_id');
}

But I'm not sure you can pass a relation like this, $this->contact_emails[???]->id, because it is a HasMany relationship. l'd rather make a custom validation where check if the current email exists in other contacts.

https://laravel.com/docs/10.x/validation#using-rule-objects

    protected array $data = [];

    public function setData(array $data): static
    {
        $this->data = $data;

        return $this;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!is_null($this->data['contactId'])) {
            $existingEmail = ContactEmail::where('email', $value)->where(
                'contact_id',
                '!=',
                $this->data['contactId']
            )->exists();

            if ($existingEmail) {
                $fail('The email already belongs to another contact.');
            }
        }
    }

After, in the validation part call it something like this: 'email' => new EmailUniqueness()

1 like
oscaribarra25's avatar

Thanks @valentin_vranic for your reply,

Yes I do have both relationships.

use Illuminate\Database\Eloquent\Model;

class Contact extends Model
{
	/**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function contact_emails()
    {
        return $this->hasMany(ContactEmail::class);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function contact_phones()
    {
        return $this->hasMany(ContactPhone::class);
    }
}

use Illuminate\Database\Eloquent\Model;

class ContactEmail extends Model
{
	/**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function contact()
    {
        return $this->belongsTo(Contact::class);
    }
}

But in my case contact_emails is not the relationship itself but an array loaded in function mount

use Livewire\Component;

class CreateEditContact extends Component
{
    //Form fields
    public $id;
	//Related models
    public $contact_emails = [];
    public $contact_phones = [];

	protected $rules = [
		'contact_emails.*.email_address' => 'required|email|max:50|unique:contact_emails,email_address',
        'contact_emails.*.email_address' => 'required|email|max:50',
        'contact_emails.*.email_type_id' => 'required',
        'contact_emails.*.is_main' => 'required',
		'contact_phones' => 'array|min:1',
		'contact_phones.*.phone_number' => 'required|string|max:12',
        'contact_phones.*.phone_type_id' => 'required',
        'contact_phones.*.is_main' => 'required'
	];

	public function mount(Contact $contact)
    {
        if ($contact->id != null)
        {
			$this->contact_emails = $contact->contact_emails->toArray();
            $this->contact_phones = $contact->contact_phones->toArray();
		}
	}
	
	public function addContactEmail()
    {
        $this->contact_emails[] = [
            'email_address' => null,
            'email_type_id' => EmailType::PERSONAL,
            'is_main' => count($this->contact_emails) == 0 ? true : false
        ];
    }

    public function removeContactEmail($index)
    {
        unset($this->contact_emails[$index]);
        $this->contact_emails = array_values($this->contact_emails);
    }

    public function addContactPhone()
    {
        $this->contact_phones[] = [
            'phone_number' => null,
            'phone_type_id' => PhoneType::PERSONAL_MOBILE,
            'is_main' => count($this->contact_phones) == 0 ? true : false
        ];
    }

    public function removeContactPhone($index)
    {
        unset($this->contact_phones[$index]);
        $this->contact_phones = array_values($this->contact_phones);
    }
}

Also the validation of uniqueness I need if for the overall DB not only for not existence in other contacts so I need contact_phones.id and contact_emails.id fields rather than contact_phones.contact_id and contact_emails.contact_id fields.

But after reading your reply I guess the only way to achieve this is using a custom data aware rule instead of using the Laravel's out of the box rule for this particular scenario.

I will create the custom rule and see where do I get.

Thanks you for your guidance.

valentin_vranic's avatar

@oscaribarra25 Well, right now I don't have any better idea. If you need only the ids, you can

->pluck('id') them from contact_phones and contact_emails

Hope you gonna figure it out

oscaribarra25's avatar

Thanks @Snapey,

This sound more like what I am looking for. I will try both recomendations yours and the one gave by @valentin_vranic to see which one fits best to my scenario.

Thanks both.

oscaribarra25's avatar

At the end I solved this according to @valentin_vranic suggestion here is the data aware rule created.

class ContactPhoneUnique implements ValidationRule, DataAwareRule
{
	protected $data = [];
	public function setData($data)
    {
        $this->data = $data;

        return $this;
    }
	public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $exists = false;
        $index = Str::replace('.phone_number', '', Str::replace('contact_phones.', '', $attribute));
        foreach ($this->data['contact_phones'] as $i => $contact_phone)
        {
            if ($i != $index && $value == $contact_phone['phone_number'])
            {
                $exists = true;
            }
        }
        if (!$exists)
        {
            $exists = ContactPhone::where('contact_phones.contact_id', '!=', $this->data['id'])
            ->where('contact_phones.phone_number', $value)
            ->exists();
        }
        if ($exists)
        {
            $fail(__('The :attribute field has already been taken.'));
        }
    }
}

And a similar rule for email uniqueness validation.

Thanks both

Please or to participate in this conversation.