This package makes it easy to add validation helpers to your Eloquent models. Some similar packages already exist, but this package aims to achieve flawless functionality in a customizable, Laravel-esque way.
The purpose of this package is to:
- Reduce code duplication by providing a single location where the baseline validation configuration is defined for your models.
- Make it easy to retrieve that configuration to supplement specific validation scenarios (like FormRequests, admin forms/actions, console input, etc.).
- Provide data integrity for applications by ensuring models adhere to a particular set of rules that is independent of UI.
- Adds several validation methods to your models like
validate()
,passesValidation()
, andfailsValidation()
. - Validation can be configured to occur automatically when saving the model (opt-in via an interface and can be turned off globally when needed).
- Validation rules can be configured as a single definition or broken out into independent rules for creating vs. updating.
- Validation rules can be superseded or mixed with custom rules at runtime.
- The data used for validation can be customized and transformed prior to validating.
- Models can define custom validation messages and attribute names.
- The validation configuration (data, rules, messages, names) are accessible via public methods, so incorporating them into validation processes with requests, controllers, Nova, Filament, etc. is easy.
- Model event hooks for
validating
andvalidated
are provided, easy to work with, and can be used in your existing model observers. - Custom validation listeners can be defined for specific model events.
- Helpers are provided to work with
Unique
rules that need to ignore the current model record when updating. - A custom ValidationException is thrown that includes the model that was validated as a property to assist with debugging, logging, and error messages.
You can install the package via composer:
composer require steven-fox/laravel-model-validation
Add validation functionality to a Model by:
- Adding the
StevenFox\LaravelModelValidation\ValidatesAttributes
trait to the model. - Defining the rules on the model via one or more of the available methods:
baseValidationRules()
,validationRulesUniqueToCreating()
,validationRulesUniqueToUpdating()
,validationRulesForCreating()
,validationRulesForUpdating()
.
use Illuminate\Database\Eloquent\Model;
use StevenFox\LaravelModelValidation\ValidatesAttributes;
class ValidatingModel extends Model
{
use ValidatesAttributes;
protected function baseValidationRules(): array
{
return [
// rules go here as ['attribute' => ['rule1', 'rule2', ...]
// like a normal validation setup
];
}
// Other methods are available for more control over rules... see below.
}
$model = new ValidatingModel($request->json());
$model->validate(); // A ModelValidationException is thrown if validation fails.
$model->save();
// Other helpful methods...
$passes = $model->passesValidation(); // An exception, will *not* be thrown if validation fails.
$fails = $model->failsValidation(); // No exception thrown here, either.
$validator = $model->validator();
You can make a model automatically perform validation when saving by adding the \StevenFox\LaravelModelValiation\Contracts\ValidatesWhenSaving
interface.
This is an opt-in feature. Without implementing this interface on your individual models, you can still perform validation on command; it simply won't be performed during the save()
process automatically.
use Illuminate\Database\Eloquent\Model;
use StevenFox\LaravelModelValidation\Contracts\ValidatesWhenSaving;
use StevenFox\LaravelModelValidation\ValidatesAttributes;
class ValidatingModel extends Model implements ValidatesWhenSaving
{
use ValidatesAttributes;
// This model will now validate upon saving.
}
By default, this package will register an event listener for the creating
and updating
model events that performs the validation prior to saving the model. You can customize this behavior by overloading the static validatingListeners()
method on your models. Here is the default implementation that you can adjust to your needs.
protected static function validatingListeners(): array
{
return [
'creating' => ValidateModel::class,
'updating' => ValidateModel::class,
];
}
Note: We specifically use the
creating
andupdating
events over the more generalsaving
event so that we don't redundantly validate a model that is "saved" without any changed attributes (which does NOT fire anupdating
event, saving us from redundancy).
Note: Keep in mind that the automatic validation process is implemented with Laravel's model event system. Thus, if you perform a
saveQuietly()
or do something else that disables/halts the model's event chain, you will disable the automatic validation as a consequence.
You can use the following methods to gain finer control over the validation rules used in particular situations.
use Illuminate\Database\Eloquent\Model;
use StevenFox\LaravelModelValidation\ValidatesAttributes;
class ValidatingModel extends Model
{
use ValidatesAttributes;
/**
* Define rules that are common to both creating and updating a model record.
*/
protected function baseValidationRules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
// more rules...
];
}
/**
* Define the rules that are unique to creating a record.
* These rules will be *combined* with the common validation rules.
*
*/
protected function validationRulesUniqueToCreating(): array
{
return ['a_unique_column' => Rule::unique('table')];
}
/**
* Define the rules that are unique to updating a record.
* These rules will be combined with the common validation rules.
*
*/
protected function validationRulesUniqueToUpdating(): array
{
return ['a_unique_column' => Rule::unique('table')->ignoreModel($this)];
}
/**
* Define the rules that are used when creating a record.
* If you overload this method on your model, the 'baseValidationRules'
* will not be used by default.
*/
protected function validationRulesForCreating(): array
{
// ...
}
/**
* Define the rules that are used when updating a record.
* If you overload this method on your model, the 'baseValidationRules'
* will not be used by default.
*/
protected function validationRulesForUpdating(): array
{
// ...
}
}
Specifying an attribute as unique
is a common validation need. Therefore, this package provides a shortcut that you can use in the baseValidationRules()
method for your unique columns. The helper function will simply define a Unique
rule for the attribute, and when the model record already exists in the database, the rule will automatically invoke the ignoreModel($this)
method.
/**
* Define rules that are common to both creating and updating a model record.
*/
protected function baseValidationRules(): array
{
return [
'email' => [
'required',
'email',
'max:255',
$this->uniqueRule(), // adds a unique rule that handles ignoring the current record on update
],
// more rules...
];
}
You can use the setSupersedingValidationRules()
method to set temporary rules that will replace all other rules defined on the model.
$model = new ValidatingModel();
$customRules = [
// ...
];
$model->setSupersedingValidationRules($customRules);
$model->validate(); // The validator will **only** use the $customRules for validation.
$model->clearSupersedingValidationRules();
$model->validate(); // The validator will now go back to using the normal rules defined on the model.
Note: You can temporarily disable a specific model instance's validation by setting the
supersedingValidationRules
to an empty array. The validation process will still run, but with no rules to validate against, the model will automatically pass.
$model = new ValidatingModel();
$model->setSupersedingValidationRules([]);
$model->validate(); // Validation will run, but with no rules defined, no actual validation will occur.
$model->clearSupersedingValidationRules();
$model->validate(); // Validation will occur normally.
You can use the addMixinValidationRules()
and setMixinValidationRules()
methods to define rules that will be merged with the other rules defined on the model. The rules you mixin for a particular attribute will replace any existing rules for that attribute.
For example, suppose your model specifies that a dateTime column must simply be a date
by default, but for a particular situation, you want to ensure that the attribute's value is a date after a particular moment. You can do this by mixing in this custom ruleset for this attribute at runtime.
$model = new ValidatingModel();
// Normally, the ValidatingModel specifies that the 'date_attribute'
// is simply a 'date'.
// However, here we will specify that it must be a date after tomorrow.
$mixinRules = [
'date_attribute' => ['date', 'after:tomorrow']
];
$model->addMixinValidationRules($mixinRules);
$model->validate(); // The validator will use a *combination* of the mixin rules and the standard rules defined within the model.
$model->clearMixinValidationRules();
$model->validate(); // The validator will now go back to using the normal rules defined on the model.
By default, this package will use the model's getAttributes()
method as the data to pass to the validator instance. Normally, the array returned from the getAttributes()
method represents the raw values that will be stored within the database. This means attributes with casting will be mutated into the format used for storage, making the validation logic as seamless as possible. For example, most date attributes on models are cast to Carbon
instances, but when validating dates, the validator needs to receive the string representation of the date, not a Carbon
instance.
If you need to customize the attributes used as data for validation, you can do so in two ways:
- Overload the
rawAttributesForValidation()
method and return what you need. - Overload the
prepareAttributesForValidation($attributes)
method to transform the default attribute values into a validation-ready state.
You can access a model's validation rules, data, messages, and attribute names using the following methods.
$model = new ValidatingModel();
// --- Rules ---
// Retrieve all rules with
$allRules = $model->validationRules();
// or, retrieve rules for certain attributes...
$specificRules = $model->validationRules('attr_1', 'attr_2', ...);
// --- Data ---
// Retrieve all validation data with
$allData = $model->validationData();
// or, retrieve the data for certain attributes...
$specificData = $model->validationData('attr_1', 'attr_2', ...);
// --- Custom Messages and Attribute Names ---
$customMessages = $model->customValidationMessages();
$customAttributeNames = $model->customValidationAttributeNames();
It is possible to disable the automatic validation during the save process for models that implement the ValidatesOnSave
interface. This can be helpful when setting up a particular test, for example.
Call the static disableValidationWhenSaving()
on a validating model class. This will disable validation until you explicitly activate it again. This is similar to the Model::unguard()
concept, and like unguarding, you would likely do the disabling of validation in the boot
method of a ServiceProvider
.
// Perhaps in a ServiceProvider...
public function boot(): void
{
ValidatingModel::disableValidationWhenSaving();
// All models that validate their attributes and implement
// the ValidatesWhenSaving interface will no longer perform
// that automatic validation during the saving process.
}
Call the static whileValidationDisables()
method, passing in a callback that executes the logic you would like to perform while automatic validation is globally disabled. This is similar to the Model::unguarded($callback)
concept.
ValidatingModel::whileValidationDisabled(function () {
// do something while automatic validation is globally disabled...
});
This package adds validating
and validated
model events. It also registers these as "observable events", which means you can listen for them within your model observer classes, like you would for saving
, deleting
, etc.
When implementing a listener for this event, the model record emitting the event and the related validator instance will be supplied to the callback.
\Illuminate\Support\Facades\Event::listen(
'eloquent.validating*',
function (Model $model, Validator $validator) {
// Do something when any model is "validating".
// $model will be an instance of Model and ValidatesAttributes.
}
);
Similar to the other observable model events, this package provides static validating($callback)
and validated($callback)
methods that you can use to register listeners for these events.
ValidatingModel::validating(function (ValidatingModel $model, Validator $validator) {
// ...
})
ValidatingModel::validated(function (ValidatingModel $model, Validator $validator) {
// ...
})
You can access the Validator instance that was last used to perform the validate()
process with the validator()
method.
$model = new ValidatingModel($request->json());
$validator = $model->validate();
// Or...
$model->validate();
$validator = $model->validator();
Note: A new validator instance is instantiated and stored on the model instance each time the
validate()
method is invoked.
$model = new ValidatingModel(...);
$validator1 = $model->validate();
$validator2 = $model->validate();
// $validator1 is *not* a reference to the same object as $validator2.
You can customize the validator instance with the beforeMakingValidator()
and afterMakingValidator($validator)
methods on a model.
Note: The
afterMakingValidator()
method can be a great place to specifyafter
hooks for your validation process.
You can pass custom validation messages and custom attribute names to the validator via the customValidationMessages()
and customValidationAttributeNames()
methods respectively.
class ValidatingModel extends Model
{
use ValidatesAttributes;
public function customValidationMessages(): array
{
return ['email.required' => 'We need to know your email address.']
}
public function customValidationAttributeNames(): array
{
return ['ip_v4' => 'ip address'];
}
}
When the validate()
method is invoked and validation fails, a ModelValidationException
is thrown by default. This exception extends Laravel's ValidationException
, but stores a reference to the model record that failed validation to make debugging or error messages easier to handle.
You can provide your own validation exception by overloading the validationExceptionClass()
or throwValidationException($validator)
methods.
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.