laravel validation: unique two fields and array - laravel

Using laravel validation, I would like to ensure that a field is unique, but in an array context. (I have seen this and this but they don't address the array context.)
Let's suppose I have this html:
<input name="sites[1][id]"><input name="sites[1][site_mrn]">
<input name="sites[2][id]"><input name="sites[2][site_mrn]">
<input name="sites[3][id]"><input name="sites[3][site_mrn]">
In my validation rule, I want to ensure that each site's id is valid, and that the site_mrn is not blank, so I have:
public function rules()
{
return [
'sites.*.site_mrn' => 'required|min:1',
'sites.*.id' => 'exists:sites,id'
];
}
So that part works. My problem is that I want to ensure that each pair of site site_id and site_mrn are unique in the mpi_sites table, but I don't know how to access each id/site_mrn pair in the input. I want to do something like this:
'sites.*' => Rule::unique('mpi_sites')->where(function ($q) {
$q->where('site_id', $xxxxx)->where('site_mrn', $yyyyy);
})

Related

Laravel unique validation must include appended text when validating

I am using the Laravel tenancy package in which there is a domains table in my database and each value in the domain column within this table is appended with .test.co.uk.
When the user enters the URL in the form, they are presented with an input element (shown above) in which they enter a URL/Domain but the .test.co.uk is already appended so the only thing they need to enter is the text that goes before that, e.g. they would enter johnsmith and in the domain column it would store johnsmith.test.co.uk. The problem I have is that I need the validation on this column to be unique but also include the .test.co.uk when performing the validation so that it looks at the value stored in the table because if a user enters johnsmith and there is currently a record in the domains table where the value is johnsmith.test.co.uk then the validation would pass but I need the validation to fail in this scenario. I am currently using a Request class which is extending the FormRequest class and have this:
public function rules()
{
return [
'url' => 'required|string|unique:domains,domain',
];
}
I have also tried a rule object but I don't think a rule object is the correct solution to this problem. Is there a convenient "Laravel" way of doing this?
In your Request class use prepareForValidation()
Docs: https://laravel.com/docs/7.x/validation#prepare-input-for-validation
protected function prepareForValidation()
{
$this->merge([
'url' => $this->url . '.test.co.uk',
]);
}

How to specify a default value for a field Laravel Nova

I want to set the default value of a resource field to the authenticated user's id. I have a model called Note which has a one to many relationship with Game and User.
User hasMany Note
Game hasMany Note
Note belongsTo User
Note belongsTo Game
In Laravel Nova my fields looks like this for the note
ID::make()->sortable(),
Text::make('Note', 'note')->onlyOnIndex(),
Textarea::make('Note', 'note')->alwaysShow(),
BelongsTo::make('Game', 'game')->hideWhenCreating()->hideWhenUpdating(),
BelongsTo::make('Created By', 'user', 'App\Nova\User')->hideWhenCreating()->hideWhenUpdating(),
DateTime::make('Created At', 'created_at')->hideWhenCreating(),
DateTime::make('Updated At', 'updated_at')->hideWhenCreating(),
Because I am referencing the Note on the Game Nova resource, when I create a Note, the game_id column is populated correctly. But, I want the user_id column to be the value of the authenticated user. It does not seem to work like this, how would I accomplish it?
If I understand correctly from the line BelongsTo::make('Created By', 'user', 'App\Nova\User')->hideWhenCreating()->hideWhenUpdating() you're trying to set a default value for the column without showing the field on the form?
I don't think this is possible in this way. As soon as you use the hide functions the fields aren't rendered and will never be passed along with the request. I tried this, and the user_id field was never sent with the request.
I think there are two ways to do this:
Show the field in the form and set the default value using the metadata (and perhaps making the field read-only for good measure).
BelongsTo::make('Created By', 'user', 'App\Nova\User')->withMeta([
"belongsToId" => auth()->user()->id,
])
See this part of the Nova docs
Or use the Eloquent creating event. The following will go in your Note model.
public static function boot()
{
parent::boot();
static::creating(function($note)
{
$note->user_id = auth()->user()->id;
});
}
Granted, the above method is a bit simple. You'd be better off using proper event listeners.
Sidenote: from an architectural point of view, I'd go with option 2. Setting a default value without getting the end-user involved sounds like a job for the Eloquent model, not for a Nova form.
You can use a method resolveUsing(). An example
<?php
//...
Select::make('My Select', 'my_custom_name')
->options(['a' => 'a', 'b' => 'b', 'c' => 'c'])
->resolveUsing(function ($value, $resource, $attribute) {
// $value = model attribute value
// $attribute = 'my_custom_name'
return 'b';
});

Unique record but only for a given parent in Laravel 5.5

I have 2 database models, both with a field for URL so you can access them. In the frontend I'd do something like this to access it:
/{{ $parent->url }}/{{ $child->url }}
Validating that the parent URL is easy, I just ensure it's required and unique for that table. But the child one is a bit tricker. I need the URL to be unique but only if the records share the same parent. Currently my validation rule for creating a new record is:
'url' => 'required|alpha_dash|unique:child_table,url',
and updated with:
'url' => 'required|alpha_dash|unique:child_table,url,' . $id,
But that means EVERY URL must be unique and I'd rather not enforce that rule. I know the parent record ID so is there a way to say URL must be unique but only for records with this parent ID?
To be absolutely clear, this isn't allowed:
/parent1/child1
/parent1/child1
But this is:
/parent1/child1
/parent2/child1
Something like this can help you achieve that:
Rule::unique('child_table', 'url')->where(function ($query) use ($parentId) {
return $query->where('parrent_id', $parentId);
})
It ended up ever so slightly more complicated if anyone ever needs this:
'url' => ['required', 'alpha_dash', Rule::unique('child_table', 'url')->where(function($query) use ($parentId) {
return $query->where('parentId', '=', $parentId);
})],

Laravel Array Validation - Continue with passed items

I would like to apply validation rules a request containing an array but handle each exception separately, and continue with the elements that passed the validation.
Lets say I have
{ students: [ {name: 'foo'}, {name: 'barbaz' ] }
and my validator looks
like this:
$validatedStudents = request()->validate([
'students.*.name' => 'required|string|max:3'
]);
I still want to continue with student 'foo' and handle student 'barbaz' separately. Laravel will throw an exception for the entire request and don't continue with any students.
One solution to this would be to loop through students and validate each student, but this is not possible since
foreach (request()->all() as $student) { ... }
will give $student as array and not request. Is it possible to run validate on array/collection like a request?
Thanks

How to combine Laravel validation required_if and required_without_all rules?

I have a situation with a subscription form, which must have different validation rules depending on user selection.
I almost complete this, but I'm stuck in a point which need a combination of rules that I think I can't get with predefined laravel rules.
As shown in the following chart, the point is when a user select invoicing preferences, with options Digital and Printed, if user option is Printed I need at least one physical address, so street address field group OR district address fields group must be mandatory.
Mandatory field unless other field is filled can be achieved by required_without_allrule, so I've trying with no success, a combination of required_if and required_without_allrules, like the following example:
public function rules()
{
return [
...
'invoicing_preferences' => 'required',
'invoicing_email' => 'email|required_if:invoicing_preferences,digital',
'invoicing_street_name' => 'string|required_if:invoicing_preferences,printed|required_without_all:invoicing_district,invoicing_parcel',
'invoicing_street_number' => 'number|required_if:invoicing_preferences,printed|required_without_all:invoicing_district,invoicing_parcel',
'invoicing_street_flat' => 'number|required_if:invoicing_preferences,printed|required_without_all:invoicing_district,invoicing_parcel',
'invoicing_street_dep' => 'alpha_num|required_if:invoicing_preferences,printed|required_without_all:invoicing_district,invoicing_parcel',
'invoicing_district' => 'alpha_num|required_if:invoicing_preferences,printed|required_without_all:invoicing_street_name, invoicing_street_number; invoicing_street_flat,invoicing_street_dep',
'invoicing_parcel' => 'alpha_num|required_if:invoicing_preferences,printed|required_without_all:invoicing_street_name, invoicing_street_number; invoicing_street_flat,invoicing_street_dep',
...
];
}
This combination doesn't work because always results in the required_with_allrule no matter if I've checked digital at the first point.
The rules() method is a method that is expected to return array of rules. Why would I write about such an obvious thing? Well, insert any kind of validation logic inside it, which means that it can also do some evaluation of posted data and gradually build up the returning array.
public function rules()
{
$this; // holds information about request itself with all the data POST-ed
if (something) {
return []; // something is true...
}
return []; // default behaviour (ehm, something is not true)
}
Another similar approach is to use multiple arrays and in the end merge them together (build them up). Which may result in nicer code. Also do not be afraid of using one or two private methods to clean up the code.

Resources