Validating fields based on old values in eloquent - laravel

Is there a way to validate model based on eloquent models old values?
For example I do not want to be able to save field status to PUBLISHED directly from DRAFT status. It has to be APPROVED to go to PUBLISHED status.
For example, I can validate a model with these rules:
private $rules = array(
'color' => 'required|alpha|min:3',
'status' => 'required',
// .. more rules here ..
);
So whenever I try to fill status with null value , validation will not pass.
What I need is also to not allow to change status from value 1 to 3, something like
private $rules = array(
'color' => 'required|alpha|min:3',
'status' => 'required|cant_be_changed_from_1_to_3_directly',
// .. more rules here ..
);

Related

How to validate laravel input only when data is present?

So I have a Laravel form where I'm collecting data, I use same form for creating and updating the table. Problem is that I only want the input fields to be validated when they contain data, this is my validation logic.
$validated = $request->validate([
'site_name' => 'sometimes|string',
'site_email' => 'sometimes|email',
'site_keywords' => 'sometimes|string',
'site_description' => 'sometimes|string',
'site_acronym' => 'sometimes|string|max:7',
'site_favicon' => 'sometimes|mimes:jpg,png,jpeg,svg',
'site_logo' => 'sometimes||mimes:jpg,png,jpeg,svg',
'site_currency' => 'sometimes|string',
'site_currency_symbol' => 'sometimes|string',
]);
But when the form is submitted, laravel returns an error for empty input fields, e.g it returns an error like so "The site name must be a string." for the first validation rule.
What am I doing wrong please?
EDIT
So I took the advice from #roj-vroemen and modified my code by using the nullable validation rule and it works just fine now.
Here's the new code block
$validated = $request->validate([
'site_name' => 'string|nullable|sometimes',
'site_email' => 'email|nullable|sometimes',
'site_keywords' => 'string|nullable|sometimes',
'site_description' => 'string|nullable|sometimes',
'site_acronym' => 'string|nullable|sometimes|max:7',
'site_favicon' => 'mimes:jpg,png,jpeg,svg|nullable|sometimes',
'site_logo' => 'mimes:jpg,png,jpeg,svg|nullable|sometimes',
'site_currency' => 'string|nullable|sometimes',
'site_currency_symbol' => 'string|nullable|sometimes',
]);
You'll want to use nullable in this case. sometimes basically means it only runs the validation for that field when it's present in the data.
In short:
Use nullable when you want to allow null values.
https://laravel.com/docs/8.x/validation#rule-nullable
Use sometimes if your only want to validate the field when it's present in the given input array.
https://laravel.com/docs/8.x/validation#validating-when-present
Note: you might want to make sure to filter out the empty values as the fields containing null will still be there:
$validated = array_filter($validated);

Two fields overwriting each others values on saving in Backpack for Laravel CRUD (spatie/laravel-tags implementation issues)?

I am trying to use spatie/laravel-tags together with Backpack for Laravel. I have 2 types of tags defined. Currently I have extended the Tag model from spatie/laravel-tags as MyCategory and MyTag and added global scopes to separate the two tag types. This works to the extent that it will display the current categories and tags correctly in Backpack, but when I try to save any changes it will only save the entries in the last field, and delete everything in the first field.
Here is my current field configuration for my CRUD:
$this->crud->addField([
'name' => 'categories',
'label' => 'Categories',
'type' => 'select2_multiple',
'tab' => 'Overview',
'attribute' => 'name',
'model' => 'App\MyCategory',
'pivot' => true,
]);
$this->crud->addField([
'name' => 'tags',
'label' => 'Tags',
'type' => 'select2_multiple',
'tab' => 'Overview',
'attribute' => 'name',
'model' => 'App\MyTag',
'pivot' => true,
]);
When I check Laravel Telescope I see that the same thing happens for both fields. First all current tags (regardless of type) for the item I am saving are deleted, and the new tags from the field are added. This is then repeated for the second field, which of course deletes the tags from the first field that should also be kept.
It seems that GlobalScope on my extended Tag models does not stick around for this part. Is there any way to reintroduce the scopes into the queries run by backpack to get these tags to save correctly?
In my CrudController I created custom update and store functions. See the update example below. This seems to work fine. There are still 2 queries being run, with the second one undoing the first one, but for my purposes this is a good enough workaround to be able to have 2 fields with different tag types in the same form in Backpack, using spatie/laravel-tags.
public function update(UpdateRequest $request)
{
$request = request();
// Merge the values from the two tag fields together into the second field
$request->merge(['tags' => array_merge((array)$request->input('categories'), (array)$request->input('tags'))]);
$redirect_location = $this->traitUpdate($request);
return $redirect_location;
}

cakephp saveAssociated and validation foreign_key fails

I have two models that we're going to name Model and RelatedModel. Model has many RelatedModel. So if I add foreign key validation on validation array like:
public $validate = array(
'foreignKey' => array(
'rule' => 'numeric',
'required' => true,
'message' => 'The id of relatedmodel should be a number'
)
)
After I create a add() function to save new registers and in this function I use saveAssociated with validation true, this one fails throwing an error 'The id of relatedmodel should be a number'.
I'm debugging the code and saveAssociated checks validation of both models at the same time and before save Model.
Is this an issue?
I think what this function should do is to validate Model, save it, add foreignKey of RelatedModel and then validate it before save.
I came into this issue only recently. It's not an issue, saveAssociated() is designed to work this way unfortunately.
What you can do is alter the required => true on the fly using the model validator. Check out the book for more information.
http://book.cakephp.org/2.0/en/models/data-validation.html#dynamically-change-validation-rules
This is working as would be expected with your given rule. required in Cake means it expects the value of foreignKey to be set in the save data prior to saving. All the validation will happen before Cake saves the data (and therefore before foreignKey is generated).
You shouldn't need to validate that it is numeric if you are allowing Cake to generate this for you behind the scenes. If you want to check that it is being passed in the data for an UPDATE you could modify the required to be only for an update like this:-
public $validate = array(
'foreignKey' => array(
'rule' => 'numeric',
'required' => 'update',
'message' => 'The id of relatedmodel should be a number'
)
)
Personally I wouldn't bother validating foreign keys unless a user is setting them rather than Cake.
Update:
To validate the foreignKey if it exists in a form submission you can drop the required option from the validation rule:-
public $validate = array(
'foreignKey' => array(
'rule' => 'numeric',
'message' => 'The id of relatedmodel should be a number'
)
);
This will allow you to pass data where the foreignKey is not present without throwing a validation error whilst validating it if it is.

Magento Admin formfield multiselect selected

I´m currently developing a custom module for magento thats going to list employees.
I have figured out almost everything. The only thing I got left is how the selected values is going to be highlighted.
The problem I´m having is for the backend.
I got 2 tabs per employee, one for employee data and one tab for magento categories.
1 employee can have 1 or more categories.
The database table that the categories are stored in are a non-eav table.
So my question is
What in a multiselect determines which values are selected? As it is now, only one value is selected.
I think you can do this by simply passing in an array of the id's to be selected into the 'value' attribute of the field being added for the multiselect in the _prepareForm() method. Something like the following.
$fieldset->addField('category_id', 'multiselect', array(
'name' => 'categories[]',
'label' => Mage::helper('cms')->__('Store View'),
'title' => Mage::helper('cms')->__('Store View'),
'required' => true,
'values' => Mage::getSingleton('mymodule/mymodel')->getMymodelValuesForForm(),
'value' => array(1,7,10),
));
The id of the form element (e.g. category_id) must not be an attribute in your model, otherwise when the form values get set with $form->setValues() later on, the attribute value will be overwritten.
I normally store multiple selections as a text column separated by commas much like most magento modules handles stores which requires a slightly different approach as shown below.
In the form block for the tab with the multiselect, you firstly define the element to be displayed like so in the _prepareForm() method. You then get the values from the model and set put them into the form data.
protected function _prepareForm()
{
...
$fieldset->addField('store_id', 'multiselect', array(
'name' => 'stores[]',
'label' => Mage::helper('cms')->__('Store View'),
'title' => Mage::helper('cms')->__('Store View'),
'required' => true,
'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true),
));
...
if ( Mage::getSingleton('adminhtml/session')->getMymodelData() )
{
$data = Mage::getSingleton('adminhtml/session')->getMymodelData();
} elseif ( Mage::registry('mymodel_data') ) {
$data = Mage::registry('mymodel_data')->getData();
}
$data['store_id'] = isset($data['stores']) ? explode(',', $data['stores']) : array();
$form->setValues($data);
}
I normally store the selected stores (categories as in your case) in the main model as a text column and comma separated values of ids, hence the explode.
In the controller for for the edit action, I put the model being edited into the mage registry so we can load it and it's values in the step above.
Mage::register('mymodel_data', $model);
Thanks for answering.
This is how my field looks like:
$fieldset->addField('npn_CatID', 'multiselect', array(
'label' => Mage::helper('employeelist')->__('Kategori'),
'class' => 'required-entry',
'required' => true,
'name' => 'npn_CatID',
'values' => $data,
'value' => array(3,5)
));
npn_CatID is the value in my db where the category id is saved.
I have tried to change the name and field ID but cant get it working.
When its the field id is like above ONE value is selected and its the last one inserted for the chosen employee
My data array looks likes
array(array('value' => '1', 'label' => 'USB'), array('value' => '2', 'label' => 'Memories'))

Validation rule for a composite unique index (non-primary)

I am sure I am not the first who has composite unique keys in tables and who wants to validate them. I do not want to invent the bicycle so I ask here first. I have several tables that have 'id' columns as primary keys and two other columns as unique composite keys. It would be nice to have a validation rule to check that the submitted entry is unique and display a validation error if it is not. In Cakephp it could be done by a custom validation rule. I am pretty sure somebody has created such method already.
Ideally it would be a method in app_model.php that could be used by different models.
I am using that function:
function checkUnique($data, $fields) {
if (!is_array($fields)) {
$fields = array($fields);
}
foreach($fields as $key) {
$tmp[$key] = $this->data[$this->name][$key];
}
if (isset($this->data[$this->name][$this->primaryKey]) && $this->data[$this->name][$this->primaryKey] > 0) {
$tmp[$this->primaryKey." !="] = $this->data[$this->name][$this->primaryKey];
}
//return false;
return $this->isUnique($tmp, false);
}
basically the usage is:
'field1' => array(
'checkUnique' => array(
'rule' => array('checkUnique', array('field1', 'field2')),
'message' => 'This field need to be non-empty and the row need to be unique'
),
),
'field2' => array(
'checkUnique' => array(
'rule' => array('checkUnique', array('field1', 'field2')),
'message' => 'This field need to be non-empty and the row need to be unique'
),
),
So basically this will show the warning under each of the fields saying that it's not unique.
I am using this a lot and it's working properly.
In the CakePHP/2.x versions released in the last few years, the isUnique rule optionally accepts several columns:
You can validate that a set of fields are unique by providing multiple
fields and set $or to false:
public $validate = array(
'email' => array(
'rule' => array('isUnique', array('email', 'username'), false),
'message' => 'This username & email combination has already been used.'
)
);
I'm not sure of the exact version when the feature was available but there's a bug fixed in core as late as October 2014 filed against 2.3 and 2.4 branches.
You could put it in app model, but my suggestion would just be to add it to the model directly by placing the rule with it's $validate property.
Check out the built in isUnique rule.

Resources