3

I've been using php for a while now, and I always wondered which is the best way to store properties in a class.

The first method is to store the data as properties.

class Foo{
    private $id;
    private $name;

    function __get($var){
        return $this->$var;
    }
}

The other method is to store the data in one array, and then use magic methods to retrieve it as a regular variable in the class.

class Bar{
    private $data;

    function __get($var){
        return $this->data[$var];
    }
}

Since both methods will achieve the same goal, which one is better, and why?

Othman
  • 2,942
  • 3
  • 22
  • 31
  • 5
    First option allows you to actually document the individual properties, and to provide individual getters/setters, so you have better control of what properties there are in the class – Mark Baker Sep 27 '13 at 07:00
  • my guess, having a large array would cause some memory problems. – trrrrrrm Sep 27 '13 at 07:04
  • @MarkBaker: Not to mention implement interfaces, better abstraction, enforcing types on properties (either through casting or type-hinting), and an improvement on overal performance (though less important for smaller projects) – Elias Van Ootegem Sep 27 '13 at 07:47

2 Answers2

4

It all depends on what you are trying to acchieve. Storing all properties in an (associative) array will make life easier for you if you wish to implement any of the Traversable interfaces, though there are ways to do this, too, with predefined properties.
If by best, you mean fastest: defining each property beforehand will make your classes more performant as I explained here already
There are, of course, a lot more reasons why the first approach is the better one I've listed them here

Basically, what you need to know is that PHP's "overloading" of objects is slow (O(1) for declared properties vs O(n) for overloaded properties). The same goes for magic methods, like __get and __set. They're slow. Consider this:

class Declared
{
    private $foo = 123;
    public function setFoo($val)
    {
        $this->foo = (int) $val;
        return $this;
    }
    public function getFoo()
    {
        return $this->foo;
    }
    public function __get($name)
    {
        $name = 'get'.ucfirst($name);
        if (method_exists($this, $name))
        {
            return $this->{$name}():
        }
        //return null or throw exception
        throw new RuntimeExcpetion('attempted to access non-existing property using '.$name.' in '.__METHOD__);
    }
    public function __set($name, $val)
    {
        $mname = 'set'.ucfirst($name);
        if (method_exists($this, $mname))
        {
            return $this->{$mname}($val):
        }
        throw new RuntimeException($name.' is an invalid property name');
    }
}

Not only can I be certain that, at no point, new properties will be added, but because I'm using custom getters and setters, I can also ensure that the type of each property is what I want/expect it to be. In this example, I need $foo to be an integer, so in the setter, I cast whatever value that is being passed as an argument to an int. You can do that in a magic __set method, too, but the more properties you're dealing with the messier that method will become (tons of if's and else's).

Compare this, now, to this class:

class NotDeclared
{
    private $data = array('foo' => 123);
    public function __get($name)
    {
        return isset($this->data[$name]) ? $this->data[$name] : null;
    }
    public function __set($name, $value)
    {
        $this->[$data] = $value;
    }
}

Now, this class does look a lot shorter, so I can see why this would appeal to anyone. Yet, let's compare both in terms of usage:

$declared = new Declared;
$notDeclared = new NotDeclared;
echo $declared->foo;//ok
echo $declared->getFoo();//ok
echo $notDeclared->foo;//ok
$declared->foo = 34;//fine
$declared->setFoo($declared->foo*2);//just fine
echo $declared->fo;//TYPO => exception
echo $notDeclared->fo;//no errors show, but echoes nothing: invisible bug?
//Worse, still: 2 mistakes in one go
$notDeclared->fo = 'Typo, meant foo, but assign string to expected integer property';
$declared->fo = 'asd';//throws excpetion at once
//singe $notDeclared->fo didn't throw excpetions, I'm assuming I reassigned $foo, yet
echo $notDeclared->foo;//ecoes old value

Because I'm restricting the usage of __set, by throwing excpetions when the user attempts to assign non-existing properties, any typo will throw an excpetion immediatly. If you don't do so, you might end up having to back-track an instance of your object, in order to spot a single, silly typo, like the example I have shown here with fo

What's more: You have to think about when the magic methods are called:

$instance->newProperty;

What is going on behind the scenes?

  • check object instance for a public property called newProperty
  • check object for method called newProperty (syntax error)
  • check object for __get method
  • invoke __get method
    1. lookup data property ($this->data)
    2. check array for newProperty key (isset)
    3. return result of ternary (ie null)

This means that $instance->newProperty requires PHP to lookup newPropery, then the __get method, then the $data property, then lookup the newPropery key in that array, and then return null, or the value. that's a lot of searching on one object.
That's why I'll always recommend the first option.

Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
0

This is more an OO paradigm question.

Magic get function is used more as a lazy way to write a getter. So you don't have to write a getter for each of your members.

The alternative would be to write a getter, like get_name(), get_id() for each function.

The second method isn't really comparable because all you have done is put all your members into an array. This is no doubt slower because at some level php has to iterate over your structure to find the appropriate key when you call $this->data[$var]

In summary. The top method is better as it more properly models what you class is supposed to represent. The second forfeits the point of classes, why have a class at all if your only member is an associative array?

ddoor
  • 5,819
  • 9
  • 34
  • 41
  • Arrays are hash tables, which are extremely fast to access without any iteration whatsoever. – deceze Sep 27 '13 at 07:19
  • Still not atomic though right? – ddoor Sep 27 '13 at 07:20
  • @deceze: yes, but predeclared properties use a HashTable of property names, that contain the offset, to be used on a C-array of `*zval`'s. a simple C array of pointers (`**zval`), with the offset gotten from a `HashTable` is about as fast as it gets in a PHP OO-context – Elias Van Ootegem Sep 27 '13 at 07:37