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
- lookup data property (
$this->data
)
- check array for
newProperty
key (isset
)
- 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.