Automatically validating Eloquent models for Laravel

Overview

Validating, a validation trait for Laravel

Build Status Total Downloads Latest Stable Version Latest Unstable Version License

Validating is a trait for Laravel 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.

Laravel 4.2+

Looking to use Validating on Laravel 4.2+? Take a look at the 0.10.x branch for documentation and installation instructions.

The Laravel 4.2 version is better suited to doing form validation; it supports custom validation messages, confirmation rules and multiple rulesets. Because Laravel 5.0 has FormRequest validation Validating is now designed to keep your core data valid and leave form validation to the framework.

Laravel 5.0 - 5.2

Looking to use Validating on Laravel 5.0 to 5.2? Take a look at the 2.x branch for documentation and installation instructions.

The Laravel 5.0 - 5.2 version used a since-deprecated ValidationException contract from the Laravel framework. For Laravel 5.3 we now extend the core validation ValidationException which means the framework will automatically redirect back with errors when a validation error occurs, much like a FormRequest would.

Laravel 5.3+

Just read on - these instructions are for you!

Installation

Simply go to your project directory where the composer.json file is located and type:

composer require watson/validating

View installation instructions for Laravel 4.2+. View installation instructions for Laravel 5.0 - 5.2.

Overview

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'
	];
}

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.

Note: you will need to set the $rules property on any models that extend from a BaseModel that uses the trait, or otherwise set an empty array as the $rules for the BaseModel. If you do not, you will inevitably end up with LogicException with message 'Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation'.

Now, you have access to some pleasant 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.

$post->saveOrFail();

You don't need to catch the exception, if you don't want to. Laravel knows how to handle a ValidationException and will automatically redirect back with form input and errors. If you want to handle it yourself though you may.

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.

Bypass validation

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();

Validation exceptions by default

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.

Validation messages

To show custom validation error messages, just add the $validationMessages property to your model.

/**
 * Validation messages to be passed to the validator.
 *
 * @var array
 */
protected $validationMessages = [
    'slug.unique' => "Another post is using that slug already."
];

Unique rules

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;

Out of the box, we support the Laravel provided unique rule. We also support the popular felixkiss/uniquewith-validator rule, but you'll need to opt-in. Just add use \Watson\Validating\Injectors\UniqueWithInjector after you've imported the validating trait.

It's easy to support additional injection rules too, if you like. Say you wanted to support an additional rule you've got called unique_ids which simply takes the model's primary key (for whatever reason). You just need to add a camel-cased rule which accepts any existing parameters and the field name, and returns the replacement rule.

/**
 * Prepare a unique_ids rule, adding a model identifier if required.
 *
 * @param  array  $parameters
 * @param  string $field
 * @return string
 */
protected function prepareUniqueIdsRule($parameters, $field)
{
    // Only perform a replacement if the model has been persisted.
    if ($this->exists) {
        return 'unique_ids:' . $this->getKey();
    }

    return 'unique_ids';
}

In this case if the model has been saved and has a primary key of 10, the rule unique_ids will be replaced with unique_ids:10.

Events

Various events are fired by the trait during the validation process which you can hook into to impact the validation process.

To hook in, you first need to add the $observeables property onto your model (or base model). This simply lets Eloquent know that your model can respond to these events.

/**
 * User exposed observable events
 *
 * @var array
 */
protected $observables = ['validating', 'validated'];

When validation is about to occur, the eloquent.validating: ModelName event will be fired, where the $event parameter will be saving or restoring. For example, if you were updating a namespaced model App\User the event would be eloquent.validating: App\User. If you listen for any of these events and return a value you can prevent validation from occurring completely.

Event::listen('eloquent.validating:*', function($model, $event) {
    // Pseudo-Russian roulette validation.
    if (rand(1, 6) === 1) {
        return false;
    }
});

After validation occurs, there are also a range of validated events you can hook into, for the passed, failed and skipped events. For the above example failing validation, you could get the event eloquent.validated: App\User.

Testing

There is currently a bug in Laravel (see issue #1181) that prevents model events from firing more than once in a test suite. This means that the first test that uses model tests will pass but any subseqeuent tests will fail. There are a couple of temporary solutions listed in that thread which you can use to make your tests pass in the meantime.

Since Laravel has switched to Liferaft for the purpose of tracking bugs and pull requests, the issue mentioned above may not be available. This Gist has an example TestCase.php which shows you how to reset the events of all your models between tests so that they work as expected.

Controller usage

There are a number of ways you can go about using the validating validating model in your controllers, however here is one example that makes use of the new FormRequest in Laravel 5 (if you'd like to see another controller example without the FormRequest, check the 4.2+ version of this package.

This example keeps your code clean by allowing the FormRequest to handle your form validation and the model to handle its own validation. By enabling validation exceptions you can reduce repetitive controller code (try/catch blocks) and handle model validation exceptions globally (your form requests should keep your models valid, so if your model becomes invalid it's an exceptional event).

<?php namespace App\Http\Controllers;

use App\Http\Requests\PostFormRequest;
use Illuminate\Routing\Controller;

class PostsController extends Controller
{
    protected $post;

    public function __construct(Post $post)
    {
        $this->post = $post;
    }

    // ...

    public function store(PostFormRequest $request)
    {
        // Post will throw an exception if it is not valid.
        $post = $this->post->create($request->input());

        // Post was saved successfully.
        return redirect()->route('posts.show', $post);
    }
}

You can then catch a model validation exception in your app/Exceptions/Handler.php and deal with it as you need.

public function render($request, Exception $e)
{
    if ($e instanceof \Watson\Validating\ValidationException) {
        return back()->withErrors($e)->withInput();
    }

    parent::render($request, $e);
}
Comments
  • Password Confirmed Not Matching

    Password Confirmed Not Matching

    Passwords are always fickle, especially when working with hashed passwords.

    I am hashing my password using the following in my Users model:

        public function setPasswordAttribute($password)
        {
            $this->attributes['password'] = Hash::make($password);
        }
    

    When I validate using the following ruleset:

        protected $fillable = [
            'email',
            'password'
        ];
    
        protected $rules = [
            'creating' => [
                'email' => 'required|email|unique:users,email',
                'password' => 'required|confirmed'
            ]
        ];
    

    I always get passwords don't match. I assume this is because of two things:

    1. The password_confirmation field is not saved in the model.
    2. The password is already hashed before the validation begins.

    Soooo, the big question is: how do you recommend dealing with password validation? :) Thanks!

    help wanted 
    opened by mikebronner 27
  • Submit Error

    Submit Error

    i got this error when submit form

    exception 'ErrorException' with message 'Missing argument 1 for Illuminate\Http\RedirectResponse::withErrors(), called in /Applications/MAMP/htdocs/admin/app/controllers/UsersController.php on line 50 and defined' in /Applications/MAMP/htdocs/admin/vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php:118 
    

    my controller

    public function store()
        {
    
            $user = new User;
            $user->username = Input::get('username');
            $user->password = Input::get('password');
            $user->email = Input::get('email');
    
    
            if ( ! $user->save())
            {
                // The user did not save due to validation errors.
                return Redirect::route('user.users.create')
                ->withErrors($e)
                ->withInput();
            }
    
            // Post was saved successfully.
            return Redirect::route('user.users.show', $user->id);
    
        }
    

    Model

    <?php
    
    use Toddish\Verify\Models\User as VerifyUser;
    use Laracasts\Presenter\PresentableTrait;
    use Watson\Validating\ValidatingTrait;
    
    class User extends VerifyUser
    {
        protected $table = 'users'; 
    
        use ValidatingTrait;
    
        protected $rules = [
            'username'  => 'required|unique:users',
            'email'     => 'required|unique:users',
            'password'  => 'required'
        ];
    
        protected $validationMessages = [
            'username.unique' => "Username Already Exist"
        ];
    
        use PresentableTrait;
    
        protected $presenter = 'UserPresenter';
    
    
    }
    
    opened by andregeges 20
  • Complete Laravel-style implementation of validating events

    Complete Laravel-style implementation of validating events

    Right now we have a simple implementation of validating events which will get you by if you need it. However, I'd like to better match up with the naming convention of events in Laravel itself. @dalabarge goes into detail in this comment.

    This is something I'd like for when we hit version 1.

    opened by dwightwatson 18
  • Purging of _confirmation, and other

    Purging of _confirmation, and other "_" attributes

    Hi,

    Love trait, thank! But purging is bit of an issue.

    Can someone please help. Seems like the trait doesn't purge _confirmation attributes from the model before saving to the persistence layer.

    Any help would be appreciated.

    Thanks!

    opened by slakbal 16
  • model validationAttributeNames

    model validationAttributeNames

    I wanna in my model like this

    public $validationAttributeNames = [
            'starts_on' => 'Starts On Date',
            'ends_on' => 'Ends On Date',
        ];
    
    opened by acodercat 13
  • With Error and With Input not working in My Modules

    With Error and With Input not working in My Modules

    Hi this is not working when i used inside my module

    i used this for my modules https://github.com/pingpong-labs/modules

    this is error message when i try

    try
    {
    $item->saveOrFail();
    }
    catch (Watson\Validating\ValidationException $e)
    {
    $errors = $e->getErrors();
    return Redirect::back()
    ->withErrors($errors)
    ->withInput();
    }
    
    Modules\Master\Models\Item model could not be persisted as it failed validation
    

    it's working fine when i used

    if ( ! $item->save())
    {
    return \Redirect::back()
    ->withErrors($item->getErrors())
    ->withInput();
    } 
    

    but error message and input not show up in my form

    opened by andregeges 12
  • Call to member function make() on a non-object online 360

    Call to member function make() on a non-object online 360

    Hello,

    I'm using standalone Eloquent with Validation trait with following class

    use Watson\Validating\ValidatingTrait;
    
    class User extends Illuminate\Database\Eloquent\Model {
    
        use ValidatingTrait;
    
        protected $table = "users";
        public $timestamps = false;
        protected $rules = [
            'name'   => 'required',
            'phone'    => 'required',
            'email' => 'required',
            'zone' => 'required'
        ];
        protected $validationMessages = [
            'name.required' => 'Please enter your name'
        ];
        protected $fillable = ['name','zone','email','phone'];
    
    

    When I do

    $user = new User(["attr"=>1,......]); $user->isValid();

    I get subject error. I'm not that familiar with eloquent but not sure what it means. Do I need to formulate my own Validate::make()?

    opened by karneaud 9
  • saveOrFail added to Illuminate\Database\Eloquent\Model.

    saveOrFail added to Illuminate\Database\Eloquent\Model.

    laravel/framework (5.1.x-dev ac2d4d4) Breaks Watson\Validating\ValidatingTrait :

    Declaration of Watson\Validating\ValidatingTrait::saveOrFail() should be compatible with Illuminate\Database\Eloquent\Model::saveOrFail(array $options = Array)

    opened by shawndalton 9
  • How are the merged rulesets implemented with isValid()?

    How are the merged rulesets implemented with isValid()?

    The documentation provides an example:

    $mergedRules = $post->mergeRulesets('saving', 'creating');
    

    How would that be implemented with the following line (but using the merged rulesets instead of the 'my_custom_rules' ruleset)?

    $post->isValid('my_custom_rules', false);
    
    feature 
    opened by zeckdude 9
  • [0.10] saving rules not applied on saving but on creating/updating

    [0.10] saving rules not applied on saving but on creating/updating

    I have a situation where i need to have the validation performed on the saving event. I expected that this would happen if I only applied one set of rules and no rulesets in my model like this

    protected $rules = [
        'name'             => 'required|min:3',
            ...
    ];
    

    Unfortunately this doesn't work as i expected it to but instead uses the ValidatingObserver::creating() and ValidatingObserver::updating() instead. I debugged the code and found the issue in ValidatingObserver::saving():

    if ( ! $model->getRuleset('creating') && ! $model->getRuleset('updating'))
    {
        return $this->performValidation($model, 'saving');
    }
    

    The issue is that ValidatingTrait::getRuleset() will merge the saving rules by default. In other words $model->getRuleset('creating') will never become false if any rule gets defined on the model.

    On an interesting sidenote in the interface the method is defined like this ValidatingInterface::getRuleset($ruleset, $mergeWithSaving = false). I am not sure if this is the only issue with this, but i would really appreciate to get back the possibility to validate on saving. =)

    opened by EloProf 8
  • $post->save() syntax saves invalid model

    $post->save() syntax saves invalid model

    Hi

    Love your work. Not sure if I have stumbled onto something, but for me this approach works ...

    $post->isValid(); // true
    

    ... and this approach does not

    if ( ! $post->save()) ...
    

    I tested this out using the 'alpha' rule and when I entered numbers, $post->isValid() - or my equivalent thereof - returned FALSE (which is good) but $post->save() actually saved the invalid model and returned TRUE.

    Hope this is a valid issue.

    opened by pspiteri 8
  • MessageBag emtpy when using

    MessageBag emtpy when using "Unique" rule on transaction

    Hey, after digging a while on a weird issue, here what I get :

    Model rule:

    'name' => [
                    'bail',
                    'required',
                    'string',
                    'max:255',
                    (new Rules\Unique('folders', 'name'))
                        ->ignore($this->id)
                        ->where('parent_id', $this->parent_id)
                        ->whereNull($this->getQualifiedDiscardedAtColumn())
                        ->whereNull($this->getQualifiedDeletedAtColumn())
                    ,
                ],
    
     DB::transaction(static function() {
                $x = new Test();
                $x->name = 'test'; # name is not unique, so it will trigger Unicity rule
                $x->saveOrFail(); # fails correctly
            });
    

    The issue is that in the following method

        /**
         * Throw a validation exception.
         *
         * @throws \Watson\Validating\ValidationException
         */
        public function throwValidationException()
        {
            $validator = $this->makeValidator($this->getRules());
    
            throw new ValidationException($validator, $this);
        }
    

    The validator is not "triggered" with a ->fail(), so message bag is empty right now. Then, my transaction rollback and the unique rule does not fails anymore, so the validator has no messages :/

    Won't it be better to do something like this :

    
        public function throwValidationException()
        {
            $validator = $this->makeValidator($this->getRules());
    
            throw new ValidationException(tap($validator)->fails(), $this);
        }
    

    in order to populate the validator with errors before anything else in the app happens ?

    opened by giulioprovasi 2
  • A suggestion to improve performValidation() function

    A suggestion to improve performValidation() function

    Hello, me again here!

    Below follows the performValidation() function from ValidatingTrait.

        /**
         * Validate the model against it's rules, returning whether
         * or not it passes and setting the error messages on the
         * model if required.
         *
         * @param  array $rules
         * @return bool
         * @throws \Watson\Validating\ValidationException
         */
        protected function performValidation($rules = [])
        {
            $validation = $this->makeValidator($rules);
            $result = $validation->passes();
            $this->setErrors($validation->messages());
            return $result;
        }
    

    I'm just wondering if we can improve this function to validate against related models in addition to the base model. Do you think this can be a good idea?

    My use case is just because i want to simplify some of my controllers. Today, they are handling validation from my services this way:

    $profile = ProfileService::update($profile, $request->all());
    
    // handle validation for all models
    if ($profile->isInvalid() || $profile->address->isInvalid()) {
        return redirect()
            ->back()
            ->withInput()
            ->withErrors(array_merge_recursive(
                $profile->getErrors()->toArray(), 
                $profile->address->getErrors()->toArray()
            ));
    }
    

    As you see, my ProfileService::update() function save profile's data into both base and related model. Both models are using this trait. I wanna keep it simple and remove that mess after redirect().

    Something like:

    $profile = ProfileService::update($profile, $request->all());
    
    // handle validation for all models at once
    if ($profile->isInvalid()) {
        $profile->throwValidationException();
    }
    
    opened by ghost 5
  • Validation incorrect work with accessors Issue #177

    Validation incorrect work with accessors Issue #177

    Issue #177 The attributesToArray method already returns attributes that are mutated and converts Carbon dates to strings. Plus, it adds appended accessors and applies type casting.

    opened by scottsteil 2
  • saveOrReturn() raises an exception!

    saveOrReturn() raises an exception!

    I've just noticed that saveOrReturn() raises an exception if $throwValidationExceptions is set true. The documentation suggests that the whole purpose of this function is to avoid throwing an exception, even if the afore-mentioned variable is set. Also I extracted this comment from the code:

    /** saveOrReturn()
     * Perform a one-off save that will return a boolean on
     * validation error instead of raising an exception.
     */
    
    opened by cartbeforehorse 0
  • [Proposal] add newOrFail(), createOrFail() and updateOrFail() methods

    [Proposal] add newOrFail(), createOrFail() and updateOrFail() methods

    It would be nice if I could do the following with my models that use the ValidatingTrait:

    function store(Request $request)
    {
        return Post::createOrFail($request->all());
    }
    
    function update(Request $request, Post $post)
    {
        return $post->updateOrFail($request->all());
    }
    

    Which would then be equivalent to:

    function store(Request $request)
    {
        $post = new Post($request->all());
    
        $post->saveOrFail()
    
        return $post;
    }
    
    function update(Request $request, Post $post)
    {
        $post->fill($request->all())->saveOrFail();
    
        return $post;
    }
    

    And just to be complete, this:

    $post = Post::newOrFail($request->all());
    

    would be equivalent to:

    $post = new Post($request->all());
    
    $post->isValidOrFail();
    
    opened by Evertt 2
Owner
Dwight Watson
#gsd
Dwight Watson
Automatically encrypt and decrypt Laravel 5 Eloquent values

Eloquent Encryption/Decryption for Laravel 5 Automatically encrypt and decrypt Laravel 5 Eloquent values. READ THIS FIRST Encrypted values are usually

Del 85 Mar 19, 2022
A small PHP library for validating VAT identification numbers (VATINs).

VATIN A small PHP library for validating VAT identification numbers (VATINs). Installation This library is available on Packagist: $ composer require

David de Boer 128 Oct 27, 2022
An Eloquent Way To Filter Laravel Models And Their Relationships

Eloquent Filter An Eloquent way to filter Eloquent Models and their relationships Introduction Lets say we want to return a list of users filtered by

Eric Tucker 1.5k Jan 7, 2023
Easy creation of slugs for your Eloquent models in Laravel

Eloquent-Sluggable Easy creation of slugs for your Eloquent models in Laravel. NOTE: These instructions are for the latest version of Laravel. If you

Colin Viebrock 3.6k Dec 30, 2022
Laravel Ban simplify blocking and banning Eloquent models.

Laravel Ban Introduction Laravel Ban simplify management of Eloquent model's ban. Make any model bannable in a minutes! Use case is not limited to Use

cybercog 879 Dec 30, 2022
cybercog 996 Dec 28, 2022
Tag support for Laravel Eloquent models - Taggable Trait

Laravel Taggable Trait This package is not meant to handle javascript or html in any way. This package handles database storage and read/writes only.

Rob 859 Dec 11, 2022
Preferences for Laravel Eloquent models

Preferences for Laravel Eloquent models Use this library to bind multiple key/value pair preferences to your application's Eloquent models. Preference

Kevin Laude 32 Oct 30, 2022
Laravel package to search through multiple Eloquent models.

Laravel package to search through multiple Eloquent models. Supports sorting, pagination, scoped queries, eager load relationships and searching through single or multiple columns.

Protone Media 845 Jan 1, 2023
Automatic human timestamps for Laravel Eloquent models.

Automatic human timestamp properties in Laravel This package provides a trait you can add to an Eloquent model that will automatically create human-re

Christopher Di Carlo 25 Jul 17, 2022
An Eloquent Way To Filter Laravel Models And Their Relationships

Eloquent Filter An Eloquent way to filter Eloquent Models and their relationships Introduction Lets say we want to return a list of users filtered by

Eric Tucker 1.5k Dec 30, 2022
🕵️ Inspect Laravel Eloquent models to collect properties, relationships and more.

??️ Eloquent Inspector Inspect Laravel Eloquent models to collect properties, relationships and more. Install Via Composer composer require cerbero/el

Andrea Marco Sartori 111 Nov 4, 2022
Laravel Nova Ban simplify blocking and banning Eloquent models.

Laravel Nova Ban Introduction Behind the scenes cybercog/laravel-ban is used. Contents Installation Usage Prepare bannable model Prepare bannable mode

cybercog 39 Sep 29, 2022
Package with small support traits and classes for the Laravel Eloquent models

Contains a set of traits for the eloquent model. In future can contain more set of classes/traits for the eloquent database.

Martin Kluska 3 Feb 10, 2022
Sortable behaviour for Eloquent models

Sortable behaviour for Eloquent models This package provides a trait that adds sortable behaviour to an Eloquent model. The value of the order column

Spatie 1.2k Dec 22, 2022
This package gives Eloquent models the ability to manage their friendships.

Laravel 5 Friendships This package gives Eloquent models the ability to manage their friendships. You can easily design a Facebook like Friend System.

Alex Kyriakidis 690 Nov 27, 2022
Create presenters for Eloquent Models

Laravel Presentable This package allows the information to be presented in a different way by means of methods that can be defined in the model's pres

The Hive Team 67 Dec 7, 2022
A small package for adding UUIDs to Eloquent models.

A small package for adding UUIDs to Eloquent models. Installation You can install the package via composer: composer require ryangjchandler/laravel-uu

Ryan Chandler 40 Jun 5, 2022
Use auto generated UUID slugs to identify and retrieve your Eloquent models.

Laravel Eloquent UUID slug Summary About Features Requirements Installation Examples Compatibility table Alternatives Tests About By default, when get

Khalyomede 25 Dec 14, 2022