How to combine Laravel validation required_if and required_without_all rules? - laravel

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.

Related

Accessor overrides custom validation rule

I have created a custom rule to make sure the number is of two decimal places. The database has numbers stored as decimal(19,4). To display the number with only two decimal places, I also have put an accessor in place. Now the problem is that the accessor overrides the custom validation rule.
the relevant part from custom validation rule
public function passes($attribute, $value)
{
$precision = Config::get('accounting.decimal');
return preg_match('/^\d+(\.\d{1,'.$precision.'})?$/',$value);
}
the accessor in model
public function getRateAttribute($value)
{
$precision = Config::get('accounting.decimal');
return round($value, $precision);
}
the validation rule in the livewire component
public function rules()
{
return [
'editing.rate' => ['required', new NumberFormat()],
];
}
Desired behavior:
Whenever a user enters a number with more than two decimal places, it should throw the customer validation error.
Every time the field is used anywhere else to display, it should show the number with two decimal places. For eg: 78.9800 should be displayed as 78.98.
any ideas why this is not happening?
Edit:
Did a little more testing and this seems to be a laravel livewire issue. I am binding data directly to the model property. Because of that, when I edit the field, the accessor is immediately called before validation occurs.
Workaround:
I canceled the data binding to the model property. This solved the issue.
Still is there a better way to use laravel-livewire data binding to model property and use an accssor that would not override the validation rule.

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

Best method to check for duplicates in store function prior to saving

I have a team-based app where one database services multiple teams. We have a custom fields table which allows for each team to create their own custom fields they want to create in addition to the global fields universally available to every team. Each custom field record has a name, type, and church_id field in the create.blade input form. Now with the nature of having separate teams, we need a system where they could create their own custom field that might share the same name of a custom field created and connected to another church team. This has been done and works just fine.
The problem is that we need to also make it so that only one custom field by a specific name can be created within the same church team. We do not want duplicate fields within the same church team. And herein lies my question, what is the best way in the store function to keep duplicates from occurring within the same team-based records. I have looked at firstOrNew, firstOrCreate, and updateOrCreate but which one is best suited to my need.
As I said, the fields we have in the form are 'name', 'type', and 'church_id' and these correspond to the custom field DB fields as 'name', 'type', and 'created_by_team_id'. What I need to have happen is for the system to check to see if there is a record matching the input 'name' that shares the same 'created_by_team_id' as the input 'church_id. If there is a record by that 'name' which also shares a marching 'id' then the system recognizes that as a duplicate and does NOT create a new record in the DB. But, if there is a record that shares the same 'name' but does not share the same 'created_by_team_id/church_id' then the system goes ahead and creates that new record because it is not a duplicate.
This is my create function:
{
if (! Gate::allows('custom_field_create')) {
return abort(401);
}
if (auth()->user()->role->contains(1)) {
$churches = Team::all();
$churchArr = array('empty' => 'Please select a church...');
foreach ($churches as $church) {
$churchArr[$church->id] = $church->name;
}
$churches->created_by_id = auth()->user()->id;
$church_id = null;
} else {
$churches = false;
$church_id = auth()->user()->team_id;
$churchArr = [];
}
return view('admin.custom_fields.create', compact('churches', 'churchArr', 'church_id'));
And this is my store function:
{
if (! Gate::allows('custom_field_create')) {
return abort(401);
}
$custom_field = CustomField::create($request->all());
$custom_field->created_by_team_id = $request->input('church_id');
$custom_field->save();
return redirect()->route('admin.custom_fields.index');
As I stated, I have been looking at and trying the firstOrNew, firstOrCreate, and updateOrCreate methods but all my attempts have been a failure. What would be the proper way to implement one of these methods to achieve my goals using my fields and DB criteria to avoid creating duplicate fields by the same name within the same team-based id?
I found the workable solution to be updateOrCreate. This allows the use of both 'name' and 'type' to be needing to be found to match. If either one is found but not the other it will create a new record. If both are found it just updates the record, which by nature avoids the duplicate creation. This allows the 'church_id' field to just added on as a filler rather than using it as one of the fields keyed on to.
$custom_field = CustomField::updateOrCreate(['name' => $request->input('name'), 'type' => $request->input('type')]);
$custom_field->created_by_team_id = $request->input('church_id');
$custom_field->save();

Yii2: How to avoid required fields in a view?

I have a view about holidays where a user uses a form to choose a place to travel and a hotel. It has two models: HolidaysPlaces and HolidaysHotels.
The user have to fill the form in this order using the view:
The user completes the fields called Place and City (related with the HolidaysPlaces model).
The user checked a checkbox if he/she wants to choose a hotel. It able a field called Hotel (related with HolidaysHotels model).
The user completes that field.
The user press a Create button.
The controller receives and saves both models.
But the problem is when the user doesn't select the checkbox (number 2 of the list): The Hotel fieldis still required (with the red asterisk as defined in its model file). So the Create button doesn't work in this case.
How can I disabled the required feature?
Add a scenario for this case in your HolidaysHotels model, and include only the fields that you want checked.
Example: If you have 3 fields name, date and age that are required, create a scenario for two only, and set the scenario in the controller. Only those two fields will be checked.
In model:
public function scenarios(){
$scenarios = parent::scenarios();
$scenarios['create'] = ['name', 'date'];
return $scenarios;
}
In controller:
$holiday = new HolidayHotels();
$holiday->scenario = 'create';
To know more about scenarios: http://www.yiiframework.com/doc-2.0/guide-structure-models.html#scenarios
You can add some condition based validation in your model rules. Here is the snippet for both client and server validation. You can many conditions inside the function block.
['field-1', 'required', 'when' => function ($model) {
return $model->check_box == '1';
}, 'whenClient' => "function (attribute, value) {
return $('#checkbox-id').is(':checked') ';
}"],
The easiest way to solve it is to send the model with empty strings. Then the controller checks if the strings are empty. If so, the model is not saved. Else, it is saved.
It was the only way that works for me.

how to combine scenarios in yii

I have an form that requires different scenarios based on selections on the form.
For example: the user can choose to add a delivery address in a shop that's different from the billing-address (this is simple, in reality, I'll have like 3 or 4 different scenarios).
So, when validating the form, depending on the user's selection, I need to combine two scenarios (the one for the billing address and the one for the delivery address).
How would I combine the two scenarios?
You can't group scenarios the the rules configuration. Instead you create a list of rules one by one.
You are able to apply more than one scenario to a set of fields.
For example :
public function rules()
{
return array(
// For purchases, supply the delivery address
// For registration, supply the postal address
// For payment, supply the delivery address and postal address
array('delivery_address', 'required', 'on' => array('purchase', 'payment'),
array('postal_address', 'required', 'on' => array('register', 'payment'),
// :
);
}
You cannot have 'conditional' scenarios. To implemented conditional rules, look at implementing custom validations.
public function rules()
{
return array(
array('postal_address', 'validateAddresses'),
);
}
public function validateAddresses($attribute,$params) {
if (!$this->deliveryAddressSameAsPostal) {
if ((empty($this->delivery_address)) {
$this->addError($attribute, 'Please supply a delivery address!');
}
}
}

Resources