I have a question regarding default values for model fields, this problem has been driving me crazy for a while.
To give you an example, I have a model with a status field that must be validated:
$validate = array (
...
'status' => array(
'list' => array(
'rule' => array('inList', array('0','1')),
'allowEmpty' => true
)
)
)
The data that is inserted in the database is not submitted from a form.
If the status field is missing, I want it to default to 1, otherwise it must be validated.
Is there a way to do this from the model without using custom validation rules? I know I can set the default value in the MySQL table, but I still want to validate it from the model, in case a different value is submitted.
EDIT
I looked at the Validation class code and the inList validation is just a stupid wrapper for an in_array check:
Cake.Utility.Validation.php
public static function inList($check, $list, $strict = true) {
return in_array($check, $list, $strict);
}
What do you know, there is also a third param that is not mentioned in the CakePHP Api docs ($strict), which defaults to true.
From the in_array docs:
If the third parameter strict is set to TRUE then the in_array()
function will also check the types of the needle in the haystack.
It still doesn't solve my problem, but now I know why array('inList', array('0','1')) was not validating empty fields even though allowEmpty was set to true.
Thank you.
You can use the beforeSave method to set a default value. Don't forget to return true or you will abort the save.
Related
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',
]);
}
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';
});
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.
Using the new style of laravel validation seen here: https://laravel-news.com/unique-and-exists-validation
I would like the name column to be required & only unique for a competition.
Using the example table below,
id 3: The validation should stop this from happening, because "a name already exist for that competition".
Custom rule
The best solution would be to create a custom rule for this, that accepts the field with the corresponding competition_id as a parameter.
Something like
//Calling the custom rule like this
['name' => 'required|validateName:yourCompetitionIdFormFieldHere'];
Declaring the custom validation function in your service provider like this
Validator::extend('validateName', function ($attribute, $value, $parameters, $validator) {
$competitionId = ($validator->data, $parameters[0]);
//Now check if the $value is already set for the specific competition_id by using normal database queries and comparison
return count(Team::where("comeptition_id", "=", $competitionId)->whereName($value)->get()) == 0
});
What does it
The custom validation rule receives the data of the field you give with the function (in the code above it's yourCompetitionIdFormFieldHere), so it knows which league/competition_id the user chose. With this information, you now can check if there is already a similar ame for the entered id.
Is there any way to prompt for validation with the attribute's label assigned via ActiveForm?
For instance I have a model attribute amount and the label defined in its attributeLabels function is "Amount"
But while generating form I neeed the label "Fees" so:
$form->field($model, 'amount')->textInput(['maxlength' => true])->label('Fees')
After validation it prompts me "Amount cannot be blank" - it is known to me that we can write a rule to change message but according to my requirements, same attribute (from same model) is having different labels on different forms.
I know that back-end implementation of default message uses:
'message' => '{attribute} cannot be blank.' does anyone know if there is any {x} by which the assigned label in ActiveForm can be retrieved?
PS: I know that this problem can be resolved by scenarios. But it would be hectic to write rule for every field which has dual label.
There is no such way, sorry! What you are doing is overriding the label within the view. Actually within the form to be more precise. If you check out what the label()-method does, you will see that it calls activeLabel() from the Html-helper. This method in turn renders your label and returns the code.
As you can see, none of this information gets written back to the model. Therefore, during the validation process, you won't have your new label at hand, because it never makes its way into the model.
Your cleanest option is the one you mentioned already. Use scenarios to decide which validation rule (and therefore message) to use. OR you could create your own public property in which you write your temporary label like so:
class MyModel extends \yii\db\ActiveRecord
{
public $myTempLabel;
public function attributeLabels()
{
$myLabel = $this->myTempLabel === null ? Yii::t('app', 'defaultLabel') : $this->myTempLabel;
return [
//other labels...
'myField'=>$myLabel,
//other labels...
];
}
}
In your view you can then set the value back into the attribute within your model.
Sorry for not being able to help better, but that's the way it's implemented!
What you need to do is handle the label changing logic in your attributeLabels() function.
class MyModel extends \yii\base\Model {
public $amount_label = 'amount';
public function attributeLabels() {
return [
'amount' => $this->amount_label
];
}
}
Then in your controller.
$model = new MyModel;
$model->amount_label = 'fees';
...
Of course you may want to set the label in a different way. For example, if your model as a type attribute, and this is the attribute which determines the label, you could do a conditional based on that type attribute.