Ardent: Laravel Models on Steroids

Share this article

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!

Frequently Asked Questions (FAQs) about Laravel Models

What are the key differences between Laravel 4.2, 5.0, 7.x, and 10.x Eloquent?

Laravel Eloquent has evolved significantly from version 4.2 to 10.x. In Laravel 4.2, Eloquent was a simple ORM (Object-Relational Mapping) with basic CRUD operations. Laravel 5.0 introduced new features like multiple connections, soft deletes, and event handling. Laravel 7.x brought in eager loading, model serialization, and API resources. Laravel 10.x has further enhanced Eloquent with improved performance, better error handling, and advanced features like batch operations and model factories.

How does Ardent enhance Laravel models?

Ardent is a package that adds a self-validating smart Eloquent model to Laravel. It provides automatic validation of attributes before they are saved, which reduces the amount of validation code you need to write in your controllers. Ardent also supports nested transaction-safe saving of models and their relationships, which simplifies complex save operations.

How do I use multiple connections in Laravel Eloquent?

In Laravel Eloquent, you can use multiple database connections by defining them in your config/database.php file. Then, in your Eloquent model, you can specify which connection to use with the $connection property. For example, protected $connection = 'mysql2'; would use the ‘mysql2’ connection.

What is eager loading in Laravel Eloquent?

Eager loading is a method of loading related data in a single database query, rather than multiple queries. This can significantly improve performance when working with large datasets. You can use the with() method in Eloquent to specify which relationships to eager load.

How do I use soft deletes in Laravel Eloquent?

Soft deletes in Laravel Eloquent allow you to ‘delete’ a record without actually removing it from the database. Instead, a deleted_at timestamp is set. You can enable soft deletes in an Eloquent model by using the SoftDeletes trait and adding a deleted_at column to your table.

What are model factories in Laravel Eloquent?

Model factories in Laravel Eloquent are a convenient way to generate new model instances for testing or seeding your database. You can define a model factory that specifies the default attribute values for your model, and then use the factory to create new instances with those defaults.

How does Ardent handle nested transaction-safe saves?

Ardent provides a saveNested() method that saves a model and all its related models in a single database transaction. This ensures that either all saves succeed, or none do, maintaining the integrity of your data.

How do I use API resources in Laravel Eloquent?

API resources in Laravel Eloquent allow you to transform your models and model collections into JSON format for use in an API. You can create a resource class that defines how the model should be transformed, and then return instances of that resource class from your API routes.

How does Ardent handle validation?

Ardent automatically validates your model’s attributes against a set of rules defined in the model before saving. If validation fails, the save operation is aborted and the validation errors are available through the errors() method.

What is the purpose of the boot() method in Laravel Eloquent?

The boot() method in Laravel Eloquent is a lifecycle hook that is called when the model is ‘booted’, i.e., loaded into memory. You can override this method in your model to add behavior that should occur whenever the model is booted, such as registering event listeners or customizing the model’s configuration.

Francesco MalatestaFrancesco Malatesta
View Author

Francesco is a web developer and consultant from Italy. He is the founder of Laravel-Italia, the official Italian Laravel Community, and writes for HTML.IT, the first italian web development portal. He also translated some books about Laravel. In the meantime he follows other projects, works as a freelance backend consultant for PHP applications and studies IT Engineering in Rome. He loves to learn new things, not only about PHP or development but everything. He hopes to work for IBM, sooner or later.

ardenteloquenthydrationlaravelmodelsOOPHPormPHPvalidation
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week