Personally I use an Observer-based auto validator that uses a nested $rules
array for the rules.
This way I can have different rules for creating and inserting records.
If you want I can set up a 'blank demo' on how I tend to do it.
Is it the best way? Well, is there a best way?
Use what suits your (or your teams) workflow best.
This is the method that suits me most.
In the model:
public $rules = [
'default' => [
'parent_id' => ['numeric'],
'number' => ['required', 'alpha_num'],
'description' => ['required'],
'stock_boxes_max' => ['numeric'],
'stock_boxes_current' => ['numeric'],
'stock_pieces_max' => ['numeric'],
'stock_pieces_current' => ['numeric'],
'stock_kilogram_max' => ['numeric'],
'stock_kilogram_current' => ['numeric'],
'tare_default' => ['numeric'],
'rollover_amount_default' => ['numeric'],
],
'create' => [
'number' => ['unique:articles,number,NULL'],
'tare_default' => ['required'],
],
'update' => [
'number' => ['unique:articles,number,{id}'],
]
];
public static function boot()
{
parent::boot();
Article::observe(new ModelObserver);
}
And in the Observer
public function saving($model)
{
return $this->validateRules($model);
}
private function validateRules($model)
{
$validation = Validator::make($model->toArray(), $this->prepareRules($model));
if ($validation->passes()) return true;
return false;
}
private function prepareRules($model)
{
$rules = $model->rules;
if ($model->exists) {
$mergedRules = array_merge_recursive($rules['default'], $rules['update']);
} else {
$mergedRules = array_merge_recursive($rules['default'], $rules['create']);
}
return $this->mergeRules($mergedRules, $model);
}
private function mergeRules($rules, $model)
{
foreach ($rules as $rulesKey => $rule) {
foreach ($rule as $key => $item) {
$rules[$rulesKey][$key] = str_replace('{id}', $model->id, $item);
}
}
return $rules;
}
I am not quite sure about how the events in Laravel are fired, how would saving then work? I guess save() triggers saving(), which calls validateRules() and then returns the boolean whether it has passed or not, correct?
The approach with nested rules seems familiar, Yii called them "scenarios".
Your idea seems good, but not perfect. I read up a bit on validation and it makes sense not to couple validation logic (the rules) in the model, as it ties it to the ORM (in this case Eloquent). But I will probably use a similar approach to automatically trigger a validation service, when saving a model.
Did I understand you correct, that you just have the one ModelObserver class in addition to your models?
insert, create, save all trigger saving and saved events.
Before and after events basically.
My observer file is in a separate folder (app/observers
, don't forget to add to composer.json
if you end up using this approach).
In the Model I link the model to the observer in the boot() function which in turn then loads the events.
I have more event watchers like this, also for keeping a logbook on create, update, delete, restore.
But this works pretty well for me.
Because the observer is it's separate file, you can load it into every model you think you need it on.
I put my rules in the model file, as (for me) that seems the most logical place; as I only need that rule-set for only that model.
And I split it up into default
(always load), create
(only load when creating) and update
(only load when updating).
Depending on the action
it will either merge default
with create
or default
with update
.
Edit: The example I gave in my first reply I'm using now for a smaller project. Normally I load the Observers through a ServiceProvider, but either way works.
In my current project at work, we can accept input for creating a petition from multiple sources - for example a web form and a CLI-based csv import. In addition, that same model has different validation rules for different points in its lifecycle. So I essentially had six different rulesets for validation:
Separating that out into validation service classes instead of trying to keep all those rules in the model really helped keep my code clean.
@thepsion5, out of curiosity.
If it all hits the same model and therefore the same database (which has the same rules regardless), would you need different rulesets past the basic create/update.
Because they all end up doing either one of those.
Maybe how it responds when it fails/succeeds, but that has little to do with the actual rules.
@thepsion5: Wouldn't this be possible if you used a nested rule set as above? That way you could reuse rules that always apply (for example name is required) and some that only apply at different stages of the process.
A problem that occurred to me just now is, what if I don't want to save the record just yet? For example I fill in a form and want to make a "preview" of it - it shouldn't get saved automatically, I just want to check if everything is valid. Another point is AJAX validation where it would be cool, to validate only one specific field at a time. (For example when you fill in a registration form and do AJAX validation, you don't want to get errors on all the empty fields while the first field is being filled.)
Also what about related model validation? Do you validate these first or is there some way to validate one model including its relations?
XoneFobic said:
@thepsion5, out of curiosity.
If it all hits the same model and therefore the same database (which has the same rules regardless), would you need different rulesets past the basic create/update.
Because they all end up doing either one of those.Maybe how it responds when it fails/succeeds, but that has little to do with the actual rules.
The rules aren't dependent on the database, they're dependent on business logic, including factors external to the model's own attributes. Trying to specify that as part of the database schema would be a nightmare.
For example, one of several date fields has to be chronologically after the date field of a related model, but that model is only set (and is required) after a certain stage in the petition's lifecycle. Sure, I could put all of that logic into the model, but again I'd end up with a single class with several hundred lines of code devoted purely to validation.
Instead, I have a BasePetitionValidator, which contains rules purely to satisfy any database or formatting constraints, and then PetitionIssuingValidator, PetitionFilingValidator, PetitionQualifyingValidator, etc that encapsulates all the business logic for validating that specific stage.
thepsion5 said: Instead, I have a BasePetitionValidator, which contains rules purely to satisfy any database or formatting constraints, and then PetitionIssuingValidator, PetitionFilingValidator, PetitionQualifyingValidator, etc that encapsulates all the business logic for validating that specific stage.
@thepsion5 — I really like the sound of this approach. Am I right in thinking that you can achieve the same shared rules + specific case rules by using the base validator? I've got a similar problem coming up in a project of mine (where objects will go through stages and have different validation requirements at each stage.
If you should share your code for these petition validators, or stub out a basic example of how to use the pattern, I'd really appreciate it.
Thanks so much
Sign in to participate in this thread!
The Laravel portal for problem solving, knowledge sharing and community building.
The community