Unique constraint in laravel by multiple fields - php

I have this table :
In the code I put :
$id = request()->route()->parameter('keyword')?->id;
return [
'key' => ['required', 'unique:keywords,key,project_page_id,'.$id],
];
If I try to add a new item with key addButton and project_page_id = 5, I get 422 error with message : The key has already been taken.. I want to have unique key by project_page_id, how can i get to this ?

Laravel has validation objects, where you get more powerful options. It has a unique validation object, and you can tweak the query, where i would think you want the following logic, to also check for the project_page_id.
'key' => ['required', (new Unique('keywords', 'id'))->where('project_page_id', $id)],

Related

Laravel 8 unique validation rule doesn't work with user_id

I'm using Laravel 8 and the unique validation rule to ensure that a record remains unique, I'm now trying to extend this so that it's unique per user as well, but when expanding the functionality and using the rule in array form it doesn't seem to validate the user ID and instead gives me a integrity constraint violation.
So I have a table called brands, and this table contains two columns in question: brand and user_id, I need to ensure that when storing a record that the brand is unique against the brand column and that the logged in in user's ID the one making the request, e.g:
Two users can have the same brand, but a single user can't have multiples of the same brand.
$validator = Validator::make($request->all(), [
'brand' => [
'required',
'string',
Rule::unique('brands')->where(function ($query) {
return $query->where('user_id', Auth::id());
})
],
'url' => 'required|string',
'telephone' => 'required|string|min:11|max:11'
]);
I've also tried:
'brand' => 'required|string|unique:brands,brand,user_id,' . Auth::id()
What am I missing?
According to the documentation you have to use the ignore() function:
Rule::unique('users')->ignore($user->id),
on your case:
Rule::unique('brands')->ignore($user->id, 'user_id'),

How to make Laravel unique validation work on an input array?

UpdateEntityRequest.php:
'phones' => 'sometimes|nullable|array',
'phones.*.id' => 'sometimes|required|integer|distinct|exists:entity_phones,id,entity_id,'.$this->id,
'phones.*.number' => 'required|alpha_num|max:255|distinct|unique:entity_phones,number,'.$this->id.',entity_id',
entity_phones table:
id, number, entity_id.
unique constraint: (number, entity_id)
EntityRepository.php:
foreach ($attributes['phones'] as $phone) {
if (isset($phone['id'])) {
$entity->phones()->updateOrCreate([
'id' => $phone['id'],
'entity_id' => $entity->id
], $phone);
} else {
$entity->phones()->create($phone);
}
}
My entity can have more than phone associated, but not a repeated number. My intention is to check the unique (entity_id, number) in the UpdateEntityRequest.php so:
If the phone object comes without an id, it should check that the combination of number, entity_id doesn't exists. But the number can exist with other entity_id.
If the request comes with an id, it should check that the combination of number, entity_id doesn't exists only in other ids, but ignore the given id.
I'm having trouble witht the Laravel Unique rule validating only when i want it to make the validation. Any ideas how could I make this solution would be appreciated.
If you need to ignore a given ID during the unique check try using the Rule class to fluently define the rule.
use Illuminate\Validation\Rule;
Validator::make($request_data, [
'number' => [
'required',
'alpha_num', (...all your other rules)
Rule::unique('entity_phones')->ignore($entity_id),
],
]);
You can read more in laravel docs about unique rule in paragraph: Forcing A Unique Rule To Ignore A Given ID.
I ended up doing this:
$phoneIds = $this->input('phones.*.id');
'phones.*.number' =>
[
'required_with:phones',
'alpha_num',
'max:255',
'distinct',
Rule::unique('entity_phones', 'number')
->where('entity_id', $this->id)
->where(function ($query) use ($phoneIds) {
return $query->where('id', '!=', array_shift($phoneIds));
})
],

Validating a JSON array in Laravel

I have a controller which receives a following POST request:
{
"_token": "csrf token omitted",
"order": [1,2,3,4,5,6,7,8]
}
How can I use validators to ensure that elements in order are unique, and between 1 and 7? I have tried the following:
$this->validate($request, [
'order' => 'required|array',
'order.*' => 'unique|integer|between:1,7'
]);
The first clause is checked, the secound one passes even when the input is invalid.
Using distinct rule:
distinct
When working with arrays, the field under validation must not have any
duplicate values.
In your case, it could look like this:
$this->validate($request, [
'order' => 'required|array',
'order.*' => 'distinct|integer|between:1,7'
]);
The unique validator keyword is for checking a value's duplicates in database.
You should use custom validator for such situations.
See: https://laravel.com/docs/5.1/validation#custom-validation-rules

Laravel 5 Unique form validation ignore id / slug

These are my rules in my class:
class AppointmentsController extends Controller
{
protected $rules = [
'appointment' => ['required', 'min:5'],
'slug' => ['required', 'unique:appointments'],
'description' => ['required'],
'date' => ['required', 'date_format:"Y-m-d H:i"'],
];
This is in the laravel official docs:
Sometimes, you may wish to ignore a given ID during the unique check.
For example, consider an "update profile" screen that includes the
user's name, e-mail address, and location. Of course, you will want to
verify that the e-mail address is unique. However, if the user only
changes the name field and not the e-mail field, you do not want a
validation error to be thrown because the user is already the owner of
the e-mail address. You only want to throw a validation error if the
user provides an e-mail address that is already used by a different
user. To tell the unique rule to ignore the user's ID, you may pass
the ID as the third parameter:
'email' => 'unique:users,email_address,'.$user->id.',user_id'
I tried using this in my rules:
'slug' => ['required', 'unique:appointments,id,:id'],
This indeed ignores the current row BUT it ignores it completely. What I want to accomplish is, I want it to ignore the current row only if the slug is unchanged. When it is changed to something that is already unique in another row, I want it to throw an error.
The Unique validator works like that
unique:table,column,except,idColumn
So in your case, you can do it like that:
Get the id you want to validate against, you can get it from the route or with any other way that works for you; something like that
$id = $this->route('id');
'slug' => ['required','unique:appointments,slug,'.$id],
For example we need to update contact info into Users table.
In my model User I created this static method:
static function getContactDataValidationRules( $idUserToExcept ) {
return [
'email' => 'required|email|max:255|unique:users,email,' . $idUserToExcept,
'pec' => 'required|email|max:255',
'phone' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'mobile' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'phone2' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'recovery_email' => 'required|email|max:255',
];
}
and in my UsersController, into the method that update User I've:
$id = $request->input('id');
$request->validate(User::getContactDataValidationRules( $id ));
:-)

How to validate if an element of an array is an array itself?

Given this input:
[
'key' => 'value',
]
How to validate to ensure that:
key attribute exists
Its value is an array (with any number of elements)
I expected this constraint to work
$constraint = new Collection([
'key' => new Required([
new Type('array'),
new Collection([
'value' => new Required([
new NotBlank(),
]),
]),
]),
]);
but it throws an exception:
Symfony\Component\Validator\Exception\UnexpectedTypeException: Expected argument of type "array or Traversable and ArrayAccess", "string" given
What am I missing?
PS: it's symfony v2.7.1
PPS: just to clarify: I know one can use a callback. If I wanted to re-implement the validation manually from scratch - I wouldn't have used symfony at the very first place. So the question is particularly about combining the existing constraints and not about using a callback constraint..
I had the exact same problem two nights ago.
The conclusion at the very end was that Symfony2 validation has no "fast-fail" validation. That is, even if your Type() constraint would fail it would proceed with other constraints and thus fail with UnexpectedTypeException exception.
However, I was able to find a way to tackle that:
$constraint = new Collection([
'key' => new Required([
new Type(['type' => 'array']),
new Collection([
// Need to wrap fields into this
// in order to provide "groups"
'fields' => [
'value' => new Required([
new NotBlank(),
]),
],
'groups' => 'phase2' // <-- THIS IS CRITICAL
]),
]),
]);
// In your controller, service, etc...
$V = $this->get('validator');
// Checks everything by `Collection` marked with special group
$violations = $V->validate($data, $constraint);
if ( $violations->count()){
// Do something
}
// Checks *only* "phase2" group constraints
$violations = $V->validate($data, $constraint, 'phase2');
if ( $violations->count()){
// Do something
}
Hope that this helps a bit. Personally, I find it annoying that we need to do this. Some sort of "fast-fail" flag within validator service would be much helpful.
You're saying the Collection constraint should just fail instead of throwing an exception because 'value' is a string and not an array.
There is a recently logged Symfony bug for this: https://github.com/symfony/symfony/issues/14943
Use Callback constraint(docs) where you can implement your custom validation logic.
The other way is to create custom constraint and validator classes. (docs)

Categories