cult3

How to create an Active Record style PHP SDK Part 4

Jul 30, 2014

Table of contents:

  1. Recap from last week
  2. Setting and getting properties
  3. Inspecting the model at runtime
  4. Conclusion

Last week we started looking at building out the abstract model that will form the foundation of this Active Record style PHP SDK.

The models are one of the most important components of the Active Record pattern due to the fact that they hold so much responsibility of the entity you are working with, as well as the means of storing those entities to the persistent storage.

In this week’s tutorial we’re going to continue looking at building the foundational layer of the abstract Model class. Today we’ll be looking at setting and getting the model properties as well as inspecting the model instance using Reflection (What is Reflection in PHP?).

Recap from last week

Before I jump back into the code, first let’s have a little recap from last week. If you haven’t already read last week’s post, you should go do that now.

The Active Record pattern works by representing each resource as a Model object. This object has the ability to find, create, update and delete entities from the storage. Each object basically maps to a single row in the database. The object also allows you to manipulate the properties of the object, as well as retrieve related entities through defined relationships.

Typical usage of the Active Record pattern would look something like this:

// Create a new user
$user = User::create(["name" => "Philip Brown"]);

// Set a property and save
$user->email = "name@domain.com";
$user->save();

// Find a user
$user = User::find(1);

// Delete a user
$user->delete();

In today’s tutorial we’re going to be looking at getting and setting the properties of the model objects as well as inspecting the objects at runtime using PHP’s ReflectionClass API.

Setting and getting properties

The first thing we’ll look at is how we get and set properties on the model objects.

There are a couple of different ways of implementing getting and setting properties on a PHP object.

From the code example above, you might be thinking that we could just implement getting and setting properties using public class properties:

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

echo $user->name; // Philip Brown

However this would be a pretty bad way of implementing this functionality. Generally speaking you don’t want to have an object’s properties set as public.

In this case I don’t want the properties of the object to be set as public because I need to be able to control what properties are set on the object in order to correctly communicate with the API.

Writing the tests

The first thing I’m going to do is to write a couple of tests to ensure that the functionality of the code we’re going to write is working correctly.

Open up the ModelTest.php file from last week and update setUp() like this:

public function setUp()
{
    $this->model = new ModelStub(new Connection(", "), ['name' => 'Philip Brown']);
}

I want to be able to inject the properties of an entity into the model when it is created so we may as well do this in the setUp() method so we don’t have to repeat this code.

Next I will need to assert that the name is being set correctly:

public function testSettingAnArrayOfAttributes()
{
    $this->assertEquals('Philip Brown', $this->model->name);
}

I also want to test to ensure that setting a property is working correctly too:

public function testSettingAProperty()
{
    $this->model->email = 'name@domain.com';
    $this->assertEquals('name@domain.com', $this->model->email);
}

Give phpunit a blast and watch those tests fail.

Now we’ll write the code to implement this functionality!

How setting and getting properties will work

As I mentioned above, setting the properties of the entity as just public properties of the object would be a really bad idea.

However setting the properties as protected object properties would also be a bad idea. One of the defining characteristics of the Active Record pattern is that the model will determine the properties of the object by reading the schema of the database.

Whilst we are using the Active Record pattern for this package, I don’t think trying to replicate this functionality when working with a third party API would be a good idea. All of the set up and infrastructure wouldn’t be worth it. However we can mimic this functionality fairly easily.

Stealing from Eloquent

I’ve mentioned a few times in this series that a really good example of an Active Record implementation in PHP is Laravel’s Eloquent.

Eloquent allows you to specify which properties of the model are fillable. This prevents the problem of Mass Assignment.

We can use this same functionality in our models to define which properties of the model should be allowed to be set.

Open up the Model.php to make the following changes.

Firstly we’re going to need to add two protected properties to the Model:

/**
 * The model's attributes
 *
 * @var array
 */
protected $attributes = [];

/**
 * The model's fillable attributes
 *
 * @var array
 */
protected $fillable = [];

The $attributes array will how the properties of the current entity such as 'Philip Brown'.

The $fillable array will hold the attributes that we want to define as being “fillable”. This will be an empty array in this abstract model, but every child model will be required to define it’s properties.

Next we’ll define a method for setting the properties on the $attributes array:

/**
 * Set attribute on object
 *
 * @param string $key
 * @param string $mixed
 * @return void
 */
protected function setAttribute($key, $value)
{
    $this->attributes[$key] = $value;
}

Next we can define a method to check to see if a property is “fillable”:

/**
 * Determine if the given attribute may be mass assigned
 *
 * @param string $key
 * @return bool
 */
protected function isFillable($key)
{
    if (in_array($key, $this->fillable)) return true;
}

Next we can define a method for stripping out only the “fillable” attributes of an array:

/**
 * Get the fillable attributes of a given array
 *
 * @param array $attributes
 * @return array
 */
protected function fillableFromArray(array $attributes)
{
    if (count($this->fillable) > 0) {
        return array_intersect_key($attributes, array_flip($this->fillable));
    }

    return $attributes;
}

Next we can define the fill() method that will accept an array of properties and then set them if they are “fillable”:

/**
 * Fill the entity with an array of attributes.
 *
 * @param array $attributes
 */
protected function fill(array $attributes)
{
    foreach ($this->fillableFromArray($attributes) as $key => $value) {
        if ($this->isFillable($key)) {
            $this->setAttribute($key, $value);
        }
    }
}

And finally we can set the __get() and __set() magic methods (What are PHP Magic Methods?) to give the illusion of getting and setting public object properties:

/**
 * Dynamically get an attribute
 *
 * @param string $key
 * @return mixed
 */
public function __get($key)
{
    if (isset($this->attributes[$key])) {
        return $this->attributes[$key];
    }

    throw new Exception("{$key} is not a valid property");
}

/**
 * Dynamically set an attribute.
 *
 * @param string $key
 * @param mixed $value
 * @return mixed
 */
public function __set($key, $value)
{
    if ($this->isFillable($key)) {
        return $this->setAttribute($key, $value);
    }

    throw new Exception("{$key} is not a valid property");
}

You’ll notice that I’m throwing an Exception in the two magic methods. Personally I find it annoying if I try to get or set a property but it just fails silently by returning null.

All credit has to go to Laravel’s Eloquent for these very simple, yet very powerful methods that allow this functionality.

Now that this functionality is set up we need to update the ModelStub class:

class ModelStub extends PhilipBrown\CapsuleCRM\Model
{
    protected $fillable = ["name", "email"];

    public function __construct(Connection $connection, $attributes = [])
    {
        parent::__construct($connection);

        $this->fill($attributes);
    }
}

Now if you run the test from earlier they should be all green!

Inspecting the model at runtime

The next thing we are going to implement is the ability to inspect the current model at runtime. This is important because we need to map the current model instance to the correct API endpoint.

For example, we need to map an instance of User to the users endpoint.

Now technically we could do this by just requiring that each model instance has a property like $endpoint set. This would solve this problem, but we’re likely going to need to get this kind of meta information about the object in a couple of different ways.

Using the PHP’s ReflectionClass also makes for a much more interesting tutorial!

Writing the tests

The first thing I’m going to do is to write out a test for how I want this functionality to work:

public function testGetPluralEntityName()
{
    $this->assertEquals('modelstubs', $this->model->base()->lowercase()->plural());
}

In the test above you’ll notice that I want to define a method called base() that will allow me to chain string modifiers together to get the name of the class, make it lowercase and then make it plural. This will allow us to have a model called User and then be able to automatically get the endpoint of users.

Run the test and watch it fail.

Now let’s implement the code to make it work.

Define the base() method

The first thing I need to do is to define a method called base() on the Model class. This method will return a new instance of a Base class (that we’ll implement in a minute):

/**
 * Return the base meta class
 *
 * @return PhilipBrown\CapsuleCRM\Meta\Base
 */
public function base()
{
    return new Base($this);
}

You’ll notice this class is injected with an instance of the current object $this. I’ve also created a new namespace Meta to hold this Base class. This is simply for a little bit of code organisation.

The Base class

Next we’ll define the Base class:

<?php namespace PhilipBrown\CapsuleCRM\Meta;

use ReflectionClass;
use PhilipBrown\CapsuleCRM\Model;

class Base
{
    /**
     * The ReflectionClass instance
     *
     * @var ReflectionClass
     */
    protected $reflection;

    /**
     * The class to inspect
     *
     * @param PhilipBrown\CapsuleCRM\model $model
     * @return void
     */
    public function __construct(Model $model)
    {
        $this->reflection = new ReflectionClass($model);
    }
}

This Base class is really just a wrapper for the ReflectionClass API. You’ll notice that in the __construct() method I’m just creating a new instance of the ReflectionClass.

The Name class

The Base class is acting as a wrapper to the ReflectionClass so we can add our own methods. We’ve already seen two of those required methods in the test lowercase() and plural(). I’ll also add uppercase() and singular() as two more methods we need to implement.

Each of these four methods are responsible for morphing a string. This is something that we can encapsulate in a dedicated class instead of bloating the Base class with extra functionality and responsibility.

Create a new file called Name.php under the Meta namespace with the following code:

<?php namespace PhilipBrown\CapsuleCRM\Meta;

class Name
{
    /**
     * The name
     *
     * @var string
     */
    protected $name;

    /**
     * Create a new Name instance
     *
     * @param string $name
     * @return void
     */
    public function __construct($name)
    {
        $this->name = $name;
    }

    /**
     * Convert the name to lowercase
     *
     * @return PhilipBrown\CapsuleCRM\Meta\Name
     */
    public function lowercase()
    {
    }

    /**
     * Convert the name to uppercase
     *
     * @return PhilipBrown\CapsuleCRM\Meta\Name
     */
    public function uppercase()
    {
    }

    /**
     * Convert the name to plural
     *
     * @return PhilipBrown\CapsuleCRM\Meta\Name
     */
    public function plural()
    {
    }

    /**
     * Convert the name to singular
     *
     * @return PhilipBrown\CapsuleCRM\Meta\Name
     */
    public function singular()
    {
    }

    /**
     * Return the name as a string
     *
     * @return string
     */
    public function __toString()
    {
        return $this->name;
    }
}

This class accepts a string``$name and sets it as a property of the class.

I’ve defined the four methods we’re going to need already. You’ll notice that each method will return a new instance of the Name class. This means we can chain the methods together to modify the name of the class in multiple ways.

Finally I’ve also defined the __toString() method so we can type hint the class as a string to return the $name. You can think of this class as if we were opening up the PHP string type and adding a couple of extra methods (if strings in PHP had methods).

Next we can implement each of the methods.

The first method we’ll look at is lowercase():

/**
 * Convert the name to lowercase
 *
 * @return PhilipBrown\CapsuleCRM\Meta\Name
 */
public function lowercase()
{
    return new Name(strtolower($this->name));
}

In this method we can simply use PHP’s inbuilt strtolower() function.

Similarly, to make strings uppercase we can just use PHP’s inbuilt function:

/**
 * Convert the name to uppercase
 *
 * @return PhilipBrown\CapsuleCRM\Meta\Name
 */
public function uppercase()
{
    return new Name(strtoupper($this->name));
}

Getting the pluralised or singular form of a word is a bit more tricky, especially because of all the weird rules of the English language.

Fortunately this is not a new problem so we can stand on the shoulders of giants. Laravel has already solved this problem for us and so we can simply using their implementation in our code.

Add the following to your composer.json file and run composer update:

"illuminate/support": "~4.1"

Next import the namespace into the Name class:

use Illuminate\Support\Pluralizer;

And finally we can define the plural() and singular methods:

/**
 * Convert the name to plural
 *
 * @return PhilipBrown\CapsuleCRM\Meta\Name
 */
public function plural()
{
    return new Name(Pluralizer::plural($this->name));
}

/**
 * Convert the name to singular
 *
 * @return PhilipBrown\CapsuleCRM\Meta\Name
 */
public function singular()
{
    return new Name(Pluralizer::singular($this->name));
}

You’ll notice that I’m not injecting the Pluralizer object into the class and I’m using static methods gasp.

This is actually a rare case when you don’t need to inject the dependency and you can use static methods.

Firstly we’re never going to need to mock the Pluralizer so we don’t need to inject it. The ability to pluralise strings is just an enhancement to strings and should really be a PHP default function anyway.

We can also freely using static methods because these method calls have no global state. A good general rule for when to use static methods is if running the method again and again with the same input will always return the same output.

Finally we can add methods to the Base class to pass the name of the current model to the Name class for modification:

<?php namespace PhilipBrown\CapsuleCRM\Meta;

use ReflectionClass;
use PhilipBrown\CapsuleCRM\Model;

class Base
{
    /**
     * The ReflectionClass instance
     *
     * @var ReflectionClass
     */
    protected $reflection;

    /**
     * The class to inspect
     *
     * @param PhilipBrown\CapsuleCRM\model $model
     * @return void
     */
    public function __construct(Model $model)
    {
        $this->reflection = new ReflectionClass($model);
    }

    /**
     * Convert the name to lowercase
     *
     * @return PhilipBrown\CapsuleCRM\Meta\ClassName
     */
    public function lowercase()
    {
        return $this->getNameInstance()->lowercase();
    }

    /**
     * Convert the name to uppercase
     *
     * @return PhilipBrown\CapsuleCRM\Meta\ClassName
     */
    public function uppercase()
    {
        return $this->getNameInstance()->uppercase();
    }

    /**
     * Convert the name to plural
     *
     * @return PhilipBrown\CapsuleCRM\Meta\ClassName
     */
    public function plural()
    {
        return $this->getNameInstance()->plural();
    }

    /**
     * Convert the name to singular
     *
     * @return PhilipBrown\CapsuleCRM\Meta\ClassName
     */
    public function singular()
    {
        return $this->getNameInstance()->singular();
    }

    /**
     * Create new Name instance
     *
     * @return PhilipBrown\CapsuleCRM\Meta\Name
     */
    protected function getNameInstance()
    {
        return new Name($this->reflection->getShortName());
    }
}

With all the code implemented we can now run the tests again and watch them pass!

Conclusion

We’ve covered quite a bit in this tutorial and so we’re well on the way to creating an solid foundation for the model objects of this package.

Firstly we creating the functionality to set properties on model instances that enable the Active Record style of public properties. We’ve abstracted this functionality into the abstract model class so all of the child classes simply need to define what properties may be set.

Secondly we create a base class so we can use PHP’s powerful Reflection API to inspect the current object at run time. This means we can determine which endpoint to hit just by looking at the current object we’re working with. We also looked at how we can encapsulate logic into smaller, dedicated classes, rather than creating monolithic classes that try to do far too much.

We’ve looked at quite a bit today, so if you want to take a look through the source code remember it’s all on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.