cult3

How to create an Active Record style PHP SDK Part 3

Jul 23, 2014

Table of contents:

  1. What are models?
  2. Why are models important in the Active Record pattern?
  3. The structure of a model
  4. Creating the abstract model
  5. Setting the Connection
  6. Conclusion

Models are one of the most important aspects of implementing the Active Record pattern. Models in the Active Record pattern inherit the ability to read, create, update and delete from the persistence storage and they also give you access to the properties and relationships of the record you are working with.

Models in the Active Record pattern tend to be pretty heavy objects because they end up having to coordinate a lot of what is going on in the code. You’ll often hear this being described as “fat model”.

In today’s article I’m going to be taking the first steps towards building out the models of this PHP SDK package.

What are models?

Before I jump into the code, first I’ll explain what I mean when I use the term model.

A model in the Active Record pattern gives you access to a particular resource of the persistence storage.

For example, you might have a users resource and a User model.

Using the model you can find() a particular user:

$user = User::find(1);

The $user object is an instance of the User model so we can now use it to set properties and save straight to the database:

$user->name = "Philip Brown";
$user->save();

The User model maps a single database record to a PHP object you can work with in your code. All of the data persistence methods such as find(), save(), update() and delete() are inherited from a base Model instance.

Why are models important in the Active Record pattern?

Models are important in the Active Record pattern because they provide the public interface for working with entities and the storage layer of our application.

When working with a model instance you can very easily get and set the properties of that record:

echo $user->name; // 'Philip Brown'

$user->email = "name@domain.com";

You can also create, update or retrieve entities from the database using an instance of the model:

$user = User::create(["name" => "Philip"]);

$user->email = "name@domain.com";
$user->save();

$user = User::find(1);
$user->update(["name" => "Philip Brown"]);

And you also have access to the entity’s related records:

$user = User::find(1);

foreach ($user->posts as $post) {
    //
}

The models of the Active Record pattern hold a lot of power and a lot of responsibility. They provide a public interface to the objects and storage and allow you to work with and manipulate the data of your application.

Arguably, models in the Active Record pattern have too much responsibility, but we’ll not worry about that for this series of articles.

The structure of a model

If you’ve been following a long with the Building Cribbb series you will probably be already familiar with Eloquent and the Active Record pattern.

However you might not know how the models of Eloquent are technically working behind the scenes.

Before I start implementing the models of the package we’re building, first I’ll talk about the structure of a model object.

The storage connection

First we need to inject the Connection dependency from last week. Whilst the Active Record pattern is coupled to the persistence layer, we still need to inject the Connection so that we can test in isolation.

Extend an abstract Model

Each of the models of the package will extend an abstract model. With the model objects having a lot of the responsibility and power we need we can abstract a lot of that functionality to an abstract class to allow all child classes to inherit and keep our code dry.

Local configuration options

Each individual model class will have their own configuration properties, methods and relationship definitions. If you’ve ever worked with Laravel’s Eloquent, this will work in exactly the same way as that.

Creating the abstract model

So the first thing to do is to create the abstract Model class that will hold a lot of the abstracted functionality of the models.

First I will create the test file:

use PhilipBrown\CapsuleCRM\Connection;

class ModelTest extends PHPUnit_Framework_TestCase
{
}

At the bottom of this file I’ll also write a ModelStub class to test this abstract class:

class ModelStub extends PhilipBrown\CapsuleCRM\Model
{
}

And finally I’ll create the actual Model class:

<?php namespace PhilipBrown\CapsuleCRM;

abstract class Model
{
}

Setting the Connection

Each model instance will require an instance of the Connection object. Without the HTTP connection this package isn’t going to be of much use.

To ensure that the Connection object is required as a dependency we can inject it through the __construct() method.

With each model object having an HTTP connection, we can work with the API using the Active Record pattern.

First we’ll write a failing test. I want to be able to inject the Connection instance and then get access to it via a public connection() method.

First I’ll create a new instance of the ModelStub object in the setUp() method:

public function setUp()
{
    $this->model = new ModelStub(new Connection(", "));
}

The setUp() will be automatically run before each test. This means we don’t have to repeat the code to create a new ModelStub instance in each test.

Next I will write the failing test:

public function testConnectionMethodHasConnection()
{
    $this->assertInstanceOf('PhilipBrown\CapsuleCRM\Connection', $this->model->connection());
}

Run phpunit and watch the test fail.

Next we can write the code to make the test pass:

<?php namespace PhilipBrown\CapsuleCRM;

abstract class Model
{
    /**
     * The HTTP connection
     *
     * @var PhilipBrown\CapsuleCRM\Connection
     */
    protected $connection;

    /**
     * Inject the Connection dependency
     *
     * @param PhilipBrown\CapsuleCRM\Connection $connection
     * @return void
     */
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * Get the connection instance
     *
     * @return PhilipBrown\CapsuleCRM\Connection
     */
    public function connection()
    {
        return $this->connection;
    }
}

Next inject the Connection into the ModelStub and pass it to the parent::__construct() method:

class ModelStub extends PhilipBrown\CapsuleCRM\Model
{
    public function __construct(Connection $connection)
    {
        parent::__construct($connection);
    }
}

Now if you run phpunit again your test should pass!

Conclusion

The Models of an Active Record are extremely important and so there is a lot to cover in order to implement them correctly. In today’s tutorial we’ve looked at the reasons why models are so important and we’ve set up the initial foundation abstract class that will hold a lot of the responsibility of each model instance.

In next week’s tutorial we will continue to look at building out the abstract model class step-by-step.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.