PHP
Article

Ardent: Laravel Models on Steroids

By Francesco Malatesta

One of the (few) things I don’t like about Laravel is that you cannot move the validation code from your controller to your models easily. When I write software I like to apply the “fat models, skinny controllers” principle. So, for me, writing the validation code in the controller is not a good thing.

To solve this, I’d like to introduce Ardent, a great package for Laravel 4. To be more precise, Ardent presents itself as “Self-validating smart models for Laravel Framework 4’s Eloquent ORM.” In other words: exactly what we need!

As you can imagine, it’s an extension of the Eloquent Model class, basically. This package comes with some new functionality, utilities and methods dedicated to input validation and other little things.

Our Test Application

For a better understanding of the advantages you can enjoy while using Ardent, we will set up a little test application. Nothing complicated: a simple To-Do List app.

Of course, I’m not going to implement a complete application: I just want to explain some principles, so I will make some controllers and models – no views. After that, I will “translate” the code using Ardent.

Our To-Do List will count two different entities:

  • User

    • id
    • first_name
    • last_name
    • email
    • password
  • Task

    • id
    • name
    • status (done / not done)

A really basic project. However, if you don’t want to write code, don’t worry: I have already prepared a migration that you can use to generate the database. Use it!

Create the migration file with the command

php artisan migrate:make todo_setup

and then, fill the file with this code:

<?php

    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;

    class TodoSetup extends Migration {

        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('users', function(Blueprint $table)
            {
                $table->increments('id')->unsigned();

                $table->string('first_name');
                $table->string('last_name');
                $table->string('email');
                $table->string('password', 60);

                $table->timestamps();
            });

            Schema::create('tasks', function(Blueprint $table)
            {
                $table->increments('id');

                $table->string('name');
                $table->boolean('status');

                $table->integer('user_id')->unsigned();

                $table->timestamps();

                $table->index('user_id');
            });
        }

        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('users');
            Schema::dropIfExists('tasks');
        }

    }

Now we have our tables. It’s time to create our models. Even here we have very few lines to write. Here’s the User model (that is also the default one).

<?php

    use Illuminate\Auth\UserTrait;
    use Illuminate\Auth\UserInterface;
    use Illuminate\Auth\Reminders\RemindableTrait;
    use Illuminate\Auth\Reminders\RemindableInterface;

    class User extends Eloquent implements UserInterface, RemindableInterface {

        use UserTrait, RemindableTrait;

        /**
         * The database table used by the model.
         *
         * @var string
         */
        protected $table = 'users';

        /**
         * The attributes excluded from the model's JSON form.
         *
         * @var array
         */
        protected $hidden = array('password', 'remember_token');

        public function tasks()
        {
            return $this->hasMany('Task');
        }

    }

I just added the tasks method to describe the relationship with the Task model.

<?php

    class Task extends \Eloquent {
        protected $fillable = [];

        public function user()
        {
            return $this->belongsTo('User');
        }
    }

We just made our starting point. From now on, right after the installation, we will see two different situations: first, the “normal” version of the code without Ardent. Right after that, we will make a comparison with the “improved” version. You will notice the difference, trust me.

Let’s start!

Installing Ardent

Installing Ardent is very easy with Composer. Just add the dependency to the composer.json file of your project.

{
        "require": {
            "laravelbook/ardent": "2.*"
        }
    }

Then, after the update, you just have to extend the Ardent class in your models like so:

<?php
    class User extends \LaravelBook\Ardent\Ardent {
        // model code here!
    }

… and you are ready to go!

Data Validation

The first thing to do is to analyze how Ardent makes our life easier in implementing validation. We already know how to do it with Laravel. Let’s make a classic example: the POST method that will handle data coming in from the form.

In a “normal” situation we would make something like this:

<?php

    public function postSignup()
    {
        $rules = array(
            'first_name' => 'required',
            'last_name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required|min:8'
        );

        $messages = array(
            'first_name.required' => 'First name is required.',
            'last_name.required' => 'Last name is required.',
            'email.required' => 'Email is required.',
            'password.required' => 'Password is required.',

            'email.email' => 'Use a real email address!',
            'email.unique' => 'This email address already exists!',
            'password.min' => 'Password must be at least 8 character long.'
        );

        $validator = Validator::make(Input::all(), $rules, $messages);

        if($validator->fails())
        {
            return Redirect::to('user/signup')->with('errors', $validator->messages());
        }

        $user = new User;

        $user->first_name = Input::get('first_name');
        $user->last_name = Input::get('last_name');
        $user->email = Input::get('email');
        $user->password = Hash::make(Input::get('password'));

        if($user->save())
        {
            $status = 1;
        }
        else
        {
            $status = 0;
        }

        return Redirect::to('user/signup')->with('status', $status);
    }

… what about with Ardent?

With Ardent, things change a little bit. First of all, as you can easily imagine, the validation rules are going to be moved directly into the model: it is here that we will start our “restyle”. So, open the model file and change it like so:

<?php

    use Illuminate\Auth\UserTrait;
    use Illuminate\Auth\UserInterface;
    use Illuminate\Auth\Reminders\RemindableTrait;
    use Illuminate\Auth\Reminders\RemindableInterface;

    class User extends \LaravelBook\Ardent\Ardent implements UserInterface, RemindableInterface {

        public static $rules = array(
            'first_name' => 'required',
            'last_name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required|min:8'
        );

        public static $customMessages = array(
            'first_name.required' => 'First name is required.',
            'last_name.required' => 'Last name is required.',
            'email.required' => 'Email is required.',
            'password.required' => 'Password is required.',

            'email.email' => 'Use a real email address!',
            'email.unique' => 'This email address already exists!',
            'password.min' => 'Password must be at least 8 character long.'
        );

        use UserTrait, RemindableTrait;

        /**
         * The database table used by the model.
         *
         * @var string
         */
        protected $table = 'users';

        /**
         * The attributes excluded from the model's JSON form.
         *
         * @var array
         */
        protected $hidden = array('password', 'remember_token');

        public function tasks()
        {
            return $this->hasMany('Task');
        }

    }

What happened? Not much: we changed the base class of our model (from Eloquent to Ardent) and we moved (using the same syntax, just a copy) the validation rules here, with the custom error messages.

So, the question now is: what are we going to write in our controller?

Let’s check it out:

<?php

    class UserController extends \BaseController {

        public function postSignup()
        {
            $user = new User;

            $user->first_name = Input::get('first_name');
            $user->last_name = Input::get('last_name');
            $user->email = Input::get('email');
            $user->password = Hash::make(Input::get('password'));

            if(!$user->save())
            {
                return Redirect::to('user/signup')->with('errors', $user->errors()->all());
            }

            return Redirect::to('user/signup')->with('status', 1);
        }

    }

No more validation instructions here. They all disappeared. It’s not just a “moving”, however: the $user->save() method, now, will return false if there are some problems in the validation phase. Then, you will be able to retrieve errors with the $user->errors()->all() method. No strange classes: the returned object will be a classic MessageBag that you maybe already met while working with Laravel.

If you prefer, however, you can also use the $user->validationErrors property. In that case, or if you want to retrieve field-specific errors just use$user->validationErrors->get('field_name').

Now, it is very likely that you are thinking “OK, but what is the real advantage, beyond fewer lines of code?”

Let’s start with the most important: better code organization means better project maintainability. In simple applications you can’t feel it as a priority, but when it comes to bigger projects things can be easily messed up with a single wrong decision. Let me make a real-world situation example. We developed our awesome To-Do List application, and it is growing really quickly. We definitely need a RESTful API for the mobile application. Using the controller in the “normal” way will mean writing another signup routine for the API. Two different blocks with the same code! This is no good. Where is the good old DRY (Don’t Repeat Yourself) principle?

So, the best practice would be writing a signup() method in the model that handles everything. Using Ardent means handling really everything: from validation to the save procedure. Without it, we could not accomplish the first phase.

A second advantage is a more practical one: model auto-hydrate. Let’s discover it together.

Model Auto-Hydrate

Our postSignup() method counts exactly thirteen lines of code. Even if it seems difficult, Ardent can lower that number further. Take a look at this example:

<?php

    class UserController extends \BaseController {

        public function postSignup()
        {
            $user = new User;
            if(!$user->save())
            {
                return Redirect::to('user/signup')->with('errors', $user->errors()->all());
            }

            return Redirect::to('user/signup')->with('status', 1);
        }

    }

No errors here. You probably already understand what happened.

Ardent has a model auto-hydrate feature. This means that when you create the object and call the $user->save() method, every field is filled automatically with the Input object data. Of course, you will have to give the right name at every form field accordingly.

So, this code:

$user = new User;

    $user->first_name = Input::get('first_name');
    $user->last_name = Input::get('last_name');
    $user->email = Input::get('email');
    $user->password = Hash::make(Input::get('password'));

    $user->save();

will have the same effect as

$user = new User;
    $user->save();

From thirtheen lines we just went down to seven for an entire signup procedure.

To use this feature, you will have to activate it. Just switch the $autoHydrateEntityFromInput in your model to true, like this:

<?php
    ...
    class User extends \LaravelBook\Ardent\Ardent {
        ...
        public $autoHydrateEntityFromInput = true;
        ...
    }

Done!

You will also often have some redundant data that you will not need for the business logic. Think about _confirmation fields or the CSRF tokens. Well, we can discard them with the model’s $autoPurgeRedundantAttributes property. As before, just switch it to true.

<?php
    ...
    class User extends \LaravelBook\Ardent\Ardent {
        ...
        public $autoHydrateEntityFromInput = true;
        public $autoPurgeRedundantAttributes = true;
        ...
    }

Now the procedure is cleaner than before.

Model Hooks

Another good feature worth mentioning is the introduction of model hooks. They are, essentially, a list of methods that, if implemented, are called in certain execution moments. So, to make an example, the afterUpdate() method will be called before every update() call. The beforeValidate() method will be called before every validation, and so on.

Here’s a list of all these methods:

  • beforeCreate()
  • afterCreate()
  • beforeSave()
  • afterSave()
  • beforeUpdate()
  • afterUpdate()
  • beforeDelete()
  • afterDelete()
  • beforeValidate()
  • afterValidate()

A classic example could be some data elaboration before the save procedure. Like this:

<?php
    public function beforeSave() {
        $this->slug = Str::slug($this->title);
        return true;
    }

Every “before” method has a boolean return value. If true, the following operation is executed normally. If the method returns false, the operation is going to be stopped. In the method above, we generated a slug (and filled the proper field) with the beforeSave() method, right after the validation.

There is also a specific tip about beforeSave() and afterSave(): you can declare them at run-time. Look:

$user->save(array(), array(), array(),
        function ($model) {
            // beforeSave() code here...
            return true;
        },
        function ($model) {
            // afterSave() code here...
        }
    );

Let’s think about a couple of possible uses of these methods in our application.

Maybe a beforeSave() to deal with password hashing?

<?php

    public function beforeSave()
    {
        $this->password = Hash::make($this->password);
    }

Or a cleaning hook right before the user delete procedure?

<?php

    public function beforeDelete()
    {
        $this->tasks()->delete();
    }

There are many possibilities.

Defining Relationships (the Ardent way)

With Ardent, you can also define relationships in a shorter way than before. Let’s see how we actually define our relationships between models: the following example shows the tasks() method in the User model.

<?php

    public function tasks()
    {
        return $this->hasMany('Task');
    }

Using Ardent to define relationships means defining a simple array, called $relationsData.

<?php

    class User extends \LaravelBook\Ardent\Ardent {

        ...

        public static $relationsData = array(
            'tasks'  => array(self::HAS_MANY, 'Task')
        );

        ...

    }

This has the exact same effect. Ardent uses the same naming convention to bind names and methods, without having to write them one by one.

However, there are many customizations that you can do:

<?php

    class User extends \LaravelBook\Ardent\Ardent {
        public static $relationsData = array(
            'books'  => array(self::HAS_MANY, 'Book', 'table' => 'users_have_books')
        );
    }

Every element in $relationsData has a key (yes, the relation’s method name) and an array with some parameters.

  • the first parameter (without a key, it’s just the first) describes the relation type (hasOne, hasMany, belongsTo, belongsToMany, morphTo, morphOne, morphMany).
  • the second parameter (without a key, it’s just the second) defines the destination model of the current relation;
  • further parameters don’t have a specific position, but a key. They could be:
    • foreignKey: optional, used for hasOne, hasMany, belongsTo and belongsToMany;
    • table, otherKey, timestamps and pivotKeys: optional, used for belongsToMany;
    • name, type and id: used with morphTo, morphOne and morphMany;

Conclusion

With a big pack of advantages (also 126k downloads and frequent updates) it’s very difficult to find a reason to not use Ardent in your next app. It is suitable for every kind of project, and being an extension of Eloquent’s Model, it achieves full compatibility with Laravel projects.

I definitely recommend it. Do you? Have you tried it out? Let us know in the comments below!

  • http://about.me/ramirovjnr Ramiro Varandas Jr

    Francesco, excellent tutorial about validation with detailed code.
    Also, Kirk Bushell gave a presentation at Laracon EU about validations and extracting it from the model, have a look.

    • Francesco Malatesta

      Hi Ramiro,
      thank you! :) I will definitely take a look to the presentation you mentioned.

      Have a good time!

  • http://ericlbarnes.com Eric Barnes

    Validation is changing and improving in Laravel 5. Checkout this video from Laracasts – https://laracasts.com/series/whats-new-in-laravel-5/episodes/3

  • Toxa

    Using mass-asignment you can write: $user = User::create(Input::all());

  • Patrick Brouwers

    We used this solution for a while, but when the project starts to scale, code began to be re-used and validation became more difficult than just simple CRUD based actions, we discovered that moving validation to a separate class and coupling it to commands (domain driven design), it became easier to write and debug more complicated actions. And, for me, it made writing tests easier, because the model was no longer in charge of all those things.
    So I am really happy with the upcoming FormRequests in Laravel 5, which has validation possibilities.

    • frostymarvelous

      I’ve experienced this when apps start sharing models.

    • Taylor Ren

      I share the same concern here. From one point of view, simple validations, like “required” can be done neatly with this approach; another point of view is that, when validation becomes more complex, for example, some validation based on some logic among several fields, I prefer to do that in a controller.

      Anyway, by all means, if we need to validate the input, there must be somewhere to inject the validation code. I really not care if it is in model, or controller (my preference).

      My reason is that if the rules are not captured in the db structure, then it is something not really a model should do.

  • frostymarvelous

    I can’t believe I’ve delayed using laravel to build an application though I use eloquent in my skimphp apps. After I saw the postSignup method after hydration, my head almost exploded! Damn that’s clean and fast!
    It could be the awesomeness of laravel or just the pot talking

  • Rezouce

    Pushing the data validation in the model violate the Single Responsability Principle.

    I think a better way to do that is to put validation in its own class.

    You can create an helper class to easily save your data and keeping your controller skinny. It’d also be reusable by just providing a different validator and model to it.

  • hot_rush

    very useful. such feature must be in models by default, like in kohana and as i remember in laravel 3…

  • Thomas Thomassen

    Is `$rules` and `$autoPurgeRedundantAttributes` supposed to be `public`? Or can (should?) they be `protected`? Looks like internal data to me.

  • David

    The template always has an empty errors variable. I tried the default template as well as blade. it seems like i am not able to write over errors variable – or i am doing something wrong. please advise

  • http://www.appsTeller.com/ Hafiz Waheed ud din

    with little advantages if your code will look unfamiliar to an average Laravel developer then it is not good unless you are getting some big advantages because this input thing can be achieved using $user->fill(Input::all())->save() instead of that Ardent Magic. Abstraction is good but should be less magical. Same is the case with validation. There is not always same simple input and validation so developer need control over things which is only possible without magic. If you will like to enter one input parameters less than all Input::all array then you can unset that and enter other if you are not using Ardent’s magical way. You can always use fill() for that. So to me Ardent just seems OK

  • http://www.appsTeller.com/ Hafiz Waheed ud din

    After reading site point tutorials, I think I should also write tutorials because i can write better or atleast on better things that are actually good. Thank you site point for motivating me ;)

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.