how to combine scenarios in yii - validation

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

Related

Laravel validation a field in an array

I'm building a farming app with Laravel and Vue. There are several activities that happen everyday in the farm and an activity might need to use products (fertilizers). In the Create.vue Activity page I'm generating multiple html inputs. A select to select a product an input to enter the quantity so I end up having a selected_products array in my request. Here's a data sample when I dd($request->all)
What is the problem?
The quantity entered in the selected_products array must be less than or equal to the quantity in stock. (can't have a product with id 9 with quantity 10 (liters or kilograms) when I only have 5 (liters or kilograms) in stock for that product)
So Product and Stock are one-to-one relationship
Product.php
public function stock () {
return $this->morphOne(Stock::class, 'stockable');
}
(I'm using morphOne here 'cause stock can also include the fuel quantity for tractors just in case you're curious)
Anyways I just want the validation to fail and return an Out of stock if the quantity in stock for that very product is less than the quantity entered
By the way after the validation I'm doing this sort of thing in my ActivityController store method so I can attach date in the activity_product table
attach_product_data = [];
foreach ($request->input('selected_products') as $product) {
$attach_product_data[$product['id']] = ['quantity' => $product['quantity']];
}
$activity->products()->attach($attach_product_data);
And this is what I'm trying to accomplish here but I have no idea how
StoreActivityRequest.php
public function rules()
{
return [
'selected_products.*.id' => ['nullable'],
'selected_products.*.quantity' => ['required_with:selected_products.*.id',
function () {
/* first we should find the product with the
`selected_products.*.id` id and check if the quantity in stock is
greater than or equal to the quanity in
`selected_products.*.quantity` */
}],
];
}
After taking a look at some data sample, you might access the ID of each field in the selected_products array in a slightly different manner than your current approach.
The idea here is to no longer validate each selected_products.*.id and selected_products.*.quantity separately but rather use a validation callback on selected_products.* key in the rules array.
Here's a code sample to explain more:
public function rules()
{
return [
'selected_products' => ['array'], // Tell Laravel that we're expecting an array
'selected_products.*' => [function ($attr, $val, $fail) {
// $val is an array that contains the "id" and "quantity" keys
// Only proceed validating when "id" is present (as i saw you're using "nullable" rule on "selected_products.*.id")
if (isset($val['id'])) {
// If no quantity is selected the raise a validation exception (based on your usage of "required_with" rule)
!isset($val['quantity']) && $fail('the quantity is required when id is present.');
// At this stage we're sure that both "id" and "quantity" are present and we might check the DB using the Product modal for example.
// If no product is found using the current "id" then raise a validation failure exception
!($prd = Product::whereId($val['id'])->first()) && fail('Unknown product with ID of ' . $val['id']);
// If the quantity in the DB is less than the selected quantity then a validation failure exception is raised as well
$prd->quantity < $val['quantity'] && fail('The selected quantity exceeds the product quantity in stock.');
}
// If no "id" is found on "selected_products.*" then nothing happens (based on your usage of "nullable" and "required_with" rules
}],
];
}
Because i have spoke of the usage of the explode function above in the comments, here's a code sample that demonstrates it's usage which cannot be applied to your current data structure by the way so I'll improvise my own structure just to demonstrate.
To use the explode function, the fields should be indexed by the IDs (9, 8 and 1 as your data sample), something like this:
With that structure we have greatly reduced the size of the data being sent in the form as we have ditched the id key entirely along with quantity key and instead we simply have Product ID => Selected Quantity.
Based on that structure, we might validate the data as follows:
public function rules()
{
return [
'selected_products' => ['array'],
'selected_products.*' => [function ($attr, $val, $fail) {
// $val contains the selected quantity and $attr can help us get the respective "id"
// We can use "explode" to get the "id"
// Based on my above data sample, $attr can be "selected_products.9", "selected_products.8" or "selected_products.1"
!($id = explode('.', $attr)[1] ?? null) && $fail('Unknown product id.');
// Fetch the DB
!($prd = Product::whereId($id) && fail('Unknown product with ID of ' . $val['id']);
// Validate the selected quantity against what we have in the DB
$prd->quantity < $val && fail('The selected quantity exceeds the product quantity in stock.');
}],
];
}
In the end, I hope i have managed to land some help. Feel free to ask for more details/explanations at any time and meanwhile here are some useful links:
explode function documentation on php.net
Laravel docs about using closures (callback functions) as custom validation rules.

Laravel Nova conditional fields on form

I'm making a create form for one of my resources using Nova. Some of the fields have conditional relationships to one another.
For example: if "is trial" is selected we must specify a value for "trial end date", but there's no point showing the "end date" field on the page if "is trial" isn't picked. Another example, fields A and B are mutually exclusive.
All of these can easily be enforced with conditional validators in the backend, and I know how to do that. I'm just trying to make an interface that's not confusing.
How can I customize the frontend JS forms for this resource to reflect such conditional relationships?
Its possible using this one
// put this inside **public function fields(Request $request)**
BelongsTo::make('Subcategoria', 'subcategory', 'App\Nova\SubCategory'),
// conditional display
$this->sellerFields(),
//used for conditional seller input
protected function sellerFields()
{
if(\Auth::user()->role == "admin"){
return $this->merge([
BelongsTo::make('Vendedor', 'user', 'App\Nova\User'),
]);
}else{
return $this->merge([]);
}
}

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 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.

CakePHP 3.x - Validation with double association

My database has Products, Shops and Branches. A Branch belongsTo a Shop, a Product belongsTo a Shop, but also optionally belongsTo a Branch.
Question: how do I (elegantly) validate from a Product whether the selected Branch is actually of the same Shop as the Shop selected for a Product?
I am already using buildRules like $rules->existsIn to verify whether the selected Shops and Branches exist in the first place. I feel like I should be able to extrapolate these functions so these are cross-checked as well.
Note: I am specifically asking for a validator. I realise I can add a constraint to the Branches->find('list')->... call so the user can only select the correct ones, however, having a validator as well seems safe.
Okay, this I solution I applied. I am not entirely satisfied because it doesn't seem elegant, but it certainly works:
public function buildRules(RulesChecker $rules)
{
// ...
$rules->add(function ($entity, $options) {
$branch = $this->Branches->find('all')->where(['Branch.id' => $entity->branch_id])->first();
if (is_null($branch))
return true;
return $branch->shop_id == $entity->shop_id;
}, 'branchCheck', ['errorField' => 'branch_id', 'message' => 'Branch does not belong to the specified shop']);
// ...
}

Resources