Validating is a trait for Laravel 4.2+ Eloquent models which ensures that models meet their validation criteria before being saved. If they are not considered valid the model will not be saved and the validation errors will be made available.
Validating allows for multiple rulesets, injecting the model ID into unique
validation rules and raising exceptions on failed validations. It's small and flexible to fit right into your workflow and help you save valid data only.
Simply add the package to your composer.json
file and run composer update
.
"watson/validating": "0.9.*"
Or go to your project directory where the composer.json
file is located and type:
composer require "watson/validating:0.9.*"
First, add the trait to your model and add your validation rules and messages as needed.
use Watson\Validating\ValidatingTrait;
class Post extends Eloquent
{
use ValidatingTrait;
protected $rules = [
'title' => 'required',
'slug' => 'required|unique:posts,slug',
'content' => 'required'
];
protected $validationMessages = [
'slug.unique' => "Another post is using that slug already."
];
}
You can also add the trait to a BaseModel
if you're using one and it will work on all models that extend from it, otherwise you can just extend Watson\Validating\ValidatingModel
instead of Eloquent
.
Now, you have access to some plesant functionality.
// Check whether the model is valid or not.
$post->isValid(); // true
// Or check if it is invalid or not.
$post->isInvalid(); // false
// Once you've determined the validity of the model,
// you can get the errors.
$post->getErrors(); // errors MessageBag
Model validation also becomes really simple.
if ( ! $post->save())
{
// Oops.
return Redirect::route('posts.create')
->withErrors($post->getErrors())
->withInput();
}
return Redirect::route('posts.show', $post->id)
->withSuccess("Your post was saved successfully.");
Otherwise, if you prefer to use exceptions when validating models you can use the saveOrFail()
method. Now, an exception will be raised when you attempt to save an invalid model.
try
{
$post->saveOrFail();
}
catch (Watson\Validating\ValidationException $e)
{
$errors = $e->getErrors();
return Redirect::route('posts.create')
->withErrors($errors)
->withInput();
}
Note that you can just pass the exception to the withErrors()
method like withErrors($e)
and Laravel will know how to handle it.
If you're using the model and you wish to perform a save that bypasses validation you can. This will return the same result as if you called save()
on a model without the trait.
$post->forceSave();
If you would prefer to have exceptions thrown by default when using the save()
method instead of having to use saveOrFail()
you can just set the following property on your model or BaseModel
.
/**
* Whether the model should throw a ValidationException if it
* fails validation. If not set, it will default to false.
*
* @var boolean
*/
protected $throwValidationExceptions = true;
If you'd like to perform a one-off save using exceptions or return values, you can use the saveOrFail()
and saveOrReturn
methods.
In some instances you may wish to use different rulesets depending on the action that is occurring. For example, you might require different rules if a model is being created to when a model is being updated. Utilising different rules is easy.
protected $rulesets = [
'creating' => [
'title' => 'required'
],
'updating' => [
'title' => 'required',
'description' => 'required'
]
];
The events that you are able to hook into with rules include creating
, updating
, saving
, deleting
and restoring
. You simply hook into a certain event by listing rules under that key.
If you want to define some default rules that will be used for all events, use the saving
ruleset. All other rulesets will extend from saving
.
protected $rulesets = [
'creating' => [
'description' => null
],
'updating' => [
'description' => 'required|min:50'
],
'deleting' => [
'user_id' => 'required|exists:users,id'
],
'saving' => [
'title' => 'required',
'description' => 'required'
]
];
In the above example you can see how the saving
ruleset is the default and the others extend it. When creating
this model the description will not be required, when updating
it the description must exist and be at least 50 characters and when it is being deleted it must have a valid user_id
set. For any event, the title will be required.
You can check to see if the model is valid with a given ruleset too.
// Is valid with 'updating' rules merged with 'saving'.
$post->isValid('updating');
// Don't merge with the 'saving' rules
$post->isValid('updating', false);
Note that if you do not pass a ruleset to any method that takes one it will default to the saving ruleset or global ruleset.
You can also define your own custom rulesets. These won't be used by the trait when hooking into model events, but you can use them to validate for yourself.
protected $rulesets = [
'my_custom_rules' => [
'title' => 'required'
]
];
// Note, your custom rules are merged with 'saving' too.
$post->isValid('my_custom_rules');
// Test if your custom rules are valid standalone.
$post->isValid('my_custom_rules', false);
There's a small helper method for merging rulesets. Pass the names of rulesets where the later ones will override the earlier ones.
$mergedRules = $post->mergeRulesets('saving', 'creating');
You may have noticed we're using the unique
rule on the slug, which wouldn't work if we were updating a persisted model. Luckily, Validation will take care of this for you and append the model's primary key to the rule so that the rule will work as expected; ignoring the current model.
You can adjust this functionality by setting the $injectUniqueIdentifier
property on your model.
/**
* Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property
* is not set in the model it will default to true.
*
* @var boolean
*/
protected $injectUniqueIdentifier = true;
You also have access to some really existing getters and setters, which allow you to get and set your validation rules and messages.
// YOLO no rules
$post->setRules([]);
// Or set a specific ruleset
$post->setRuleset([], 'creating');
// Be a little nicer
$post->setMessages(['title.required' => "Please, please set a title."])
These are handy if you need to adjust the rules or messages in a specific scenario differently.
Various events are fired by the trait during the validation process which you can hook into to impact the validation process.
When validation is about to occur, the validating.$event
event will be fired, where $event
will be saving
, creating
, updating
, deleting
or restoring
. If you listen for any of these events and return a value you can prevent validation from occurring completely.
Event::listen('validating.*', function($model)
{
// Psuedo-Russian roulette validation.
if (rand(1, 6) === 1)
{
return false;
}
}
});
After validation occurs, either validating.passed
or validating.failed
will be fired depending on the state of the validation.
There are a few ways to go about using the validating model in your controllers, but here's the simple way I like to do it. Really clean, clear as to what is going on and easy to test. Of course you can mix it up as you need, it's just one approach.
class PostsController extends BaseController
{
protected $post;
public function __construct(Post $post)
{
$this->post = $post;
}
// ...
public function store()
{
// We can use all input if we have the $fillable property
// set on our model.
$input = Input::all();
$post = $this->post->fill($input);
if ( ! $post->save())
{
// The post did not save due to validation errors.
return Redirect::route('posts.create')
->withErrors($e)
->withInput();
}
// Post was saved successfully.
return Redirect::route('posts.show', $post->id);
}
}
It's important to note that $post->save()
should only return false if validation fails (unless you have other observers watching your model events). If there is an issue with saving in the database your app would raise an exception instead.
You might also like to reduce the number of lines in your code by doing the above test all in one line...
if ( ! $this->post->create(Input::all()))
{
//
}