Marc-Olivier Fiset Aspiring at Life and Software Development

Simulating Computed Properties in PHP

Coming from a .NET background, I definitely miss C# properties. Let's say you have a class that's got a FirstName and a LastName property, defined as follows :

public class Person
{
    public String FirstName { get; set; }
    public String LastName { get; set; }
}

Then you might want to have another property FullName, that concatenates the first and the last name together:

public String FullName
{
    get { return FirstName + " " + LastName; }
}

You would use it like so :

var person = new Person();

person.FirstName = "Marco";
person.LastName = "Fiset";

Console.WriteLine(person.FullName);
// => Marco Fiset

Pretty straigthforward. Now let's try to do the same thing with PHP :

class Person
{
    public $firstName;
    public $lastName;

    public function fullName()
    {
        return $this->firstName . ' ' . $this->lastName;
    }
}

$person = new Person();

$person->firstName = 'Marco';
$person->lastName = 'Fiset';

echo $person->fullName();
// => Marco Fiset

Oh God, this is awful. The thing is, in order to have a computed property in PHP, I had to use a function. I might be the only one, and you might call me crazy, but that annoys the hell out of me. Let's echo each property one after the other so you can better see what makes me cringe:

echo $person->firstName;
echo $person->lastName;
echo $person->fullName(); // Argh! Parentheses!!! >8(

I hate it! Why would fullName be different because he's a computed value? I'm pretty sure the other fields are making fun of his parentheses when we look away. Poor guy, he doesn't deserve this. He's only doing his job the best way he knows. fullName would love to have his handicap removed, but PHP won't let him do that. Fortunately, we can fix that for him.

But... how can we compute a value without using a function !?

Well, suffer no more, because there is a way we can do this. Let me introduce one of my favorite features of PHP:

Magic Methods

You know a language is awesome when it's got a feature called Magic Methods.

Magic methods are a set of functions that you can define on an object, that will be invoked magically in some given circumstances. Here is the full list of magic methods:

__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone(),

Head over to the PHP documentation if you want to know more on this topic. The function that we will use today is __get(), which has the following signature:

public mixed __get(string $name);

Don't worry, we'll be covering more of those functions in upcoming posts.

I'll start straight with an example and then I'll explain what's going on:

class Foo
{
    public $bar = 'bar';
    public $baz = 'baz';

    public function __get($name)
    {
        return "Property $name not found.";
    }
}

$foo = new Foo();

echo $foo->bar;
// => bar
echo $foo->baz;
// => baz
echo $foo->qux;
// => Property qux not found

I bet you can understand what just happened. __get() acts as a catch-all for object properties. When we try to access a property that's not defined on an object, its __get() method is magically invoked, receiving the name of the property we tried to call as an argument. Now we can use that in a way to make our computed properties work.

Computed Properties

How are we going to apply this to make our computed properties work? Reusing my fullName example from the beginning, I can implement it in a very simple way :

class Person
{
    public $firstName;
    public $lastName;

    public function __get($name)
    {
        if ($name == 'fullName')
            return $firstName . ' ' . $lastName;
    }
}

$person = new Person();

$person->firstName = 'Marco';
$person->lastName = 'Fiset';

echo $person->fullName;
// => Marco Fiset

Ha! Much better, much better! Now let's say we want to define another computed property, representing the last name, followed by a comma, then the first name. We could call this property reversedFullName. Let's go ahead and modify our __get() method to include the new property:

class Person
{
    // ...

    public function __get($name)
    {
        if ($name == 'fullName')
            return $firstName . ' ' . $lastName;

        if ($name == 'reversedFullName')
            return $lastName . ', ' . $firstName;
    }
}

// ...

echo $person->reversedFullName;
// => Fiset, Marco

And it works! However you can probably see that this will grow out of hand very quickly. We need to have a way to separate all those properties, instead of cluttering the __get() function with every one of them. Here is a better version using dynamic dispatching:

public function __get($name)
{
    if (method_exists($this, $name))
        return $this->$name();
}

private function fullName()
{
    return $firstName . ' ' . $lastName;
}

private function reversedFullName()
{
    return $lastName . ', ' . $firstName;
}

As you might have already guessed, it still works. And it's much cleaner. Now every computed property gets its own method. The __get() function only checks if the current object has defined a method with the same name as the property we tried to access, and if yes, returns the result of that function. Pretty simple, huh?

The next step would be to take this __get() function and put it somewhere reusable. We certainly don't want to redefine it in every object where we want to use computed properties.

What about a base class called ComputedProperties that we could derive from?

Well, a base class would work, that's for sure. However, that might not be the best solution. Since PHP does not allow multiple inheritance of classes, our objects could not derive any class other than ComputedProperties. So that's out of question. Fortunately for us, PHP has got another trick up its sleeves.

Traits

Traits have been introduced as part of PHP 5.4. You can more about that feature in the PHP documentation. Here is an exerpt from this page explaining what Traits consist of:

Traits are a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.

What this means is that traits allow us to import functionnality into a class. In our case, it fits the bill perfectly. We can create a simple trait that will contain our computed property shenanigan:

trait ComputedProperties
{
    public function __get($name)
    {
        if (method_exists($this, $name))
            return $this->$name();
    }
}

Can't really go simpler than that. We can now use the trait in our Person class:

class Person
{
    use ComputedProperties;

    // ...
}

Aaaaannd we're done! What this essentially does is that it includes in our class everything that is contained inside of the trait. You can think of it as copy-pasting the trait's content into our class. The trait has access to everything in the class that imports it, even private methods, and that is the reason why it still works even though our methods are private.

That sums up this tutorial, I hope you had fun and that you learnt something new! I created a PHP Runnable with the full code so you can try it and experiment on your own.