Virtual Getters vs Actual Getters
Writing getters and setters sucks, especially if you have a lot of properties in your classes. A method I've used in the past is to create a Base
class from which all other classes are extended. This base class uses the magic method __call()
to provide virtual getters and setters.
class Base
{
public function __call ( $method, $arguments )
{
$type = substr($method, 0, 3);
$property = substr($method, 3);
$property[0] = strtolower($property[0]);
if (property_exists($this, $property))
{
switch ($type)
{
case 'get':
return $this->$property;
case 'set':
$this->$property = $arguments[0];
}
}
}
}
Derived subclasses of Base
automagically allow protected properties to be accessed.
class ExtendedBaseClass extends Base
{
protected $foo = 'bar';
}
$obj = new ExtendedBaseClass();
echo $obj->getFoo();
// outputs "bar"
While this method is certainly conventient, it introduces the overhead of processing the calls to each getter and setter.
Hypothesis
The overhead of using a Base
class to provide virtual getters is significantly higher than using actual getters.
Setup
Using the two classes above, plus a third class implementing actual getters, we can test the time it takes to retrieve the value of a property from an object instantiated from each class.
The third class implementing actual getters is
class StandaloneClass
{
protected $foo = 'bar';
public function getFoo ()
{
return $this->foo;
}
}
We'll test how long it takes to retrieve the value, repeat the process 1000 times and find the average time for each retrieval.
The code for the test is
function runTest ( $obj, $repeat = 1000 )
{
$tests = array();
$totalTimeStart = microtime(true);
for ($i=0; $i<$repeat; $i++)
{
$start = microtime(true);
$obj->getFoo();
$end = microtime(true);
$tests[] = $end-$start;
}
$totalTimeEnd = microtime(true);
$totalTime = round((($totalTimeEnd - $totalTimeStart) * 10000), 2);
$avg = 0;
foreach ($tests as $time)
{
$avg += $time;
}
$avg = round(($avg / $repeat * 10000), 5); // find average microseconds
echo 'ran test '.$repeat.' times'."\n".
'average time is '.$avg.' microseconds'."\n";
}
echo "\n";
$obj = new StandaloneClass();
echo 'StandaloneClass->getFoo() returns "'.$obj->getFoo().'"'."\n";
runTest($obj);
echo "\n";
$obj = new ExtendedBaseClass();
echo 'ExtendedBaseClass->getFoo() returns "'.$obj->getFoo().'"'."\n";
runTest($obj);
echo "\n";
Results
Test 1
StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02589 microseconds
ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.17511 microseconds
Test 2
StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02607 microseconds
ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.13673 microseconds
Test 3
StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02705 microseconds
ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.13339 microseconds
Test 4
StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02743 microseconds
ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.13734 microseconds
Test 5
StandaloneClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.02751 microseconds
ExtendedBaseClass->getFoo() returns "bar"
ran test 1000 times
average time is 0.17482 microseconds
Conclusion
While using virtual getters and setters is convenient, it introduces considerable overhead. The results show that the actual getter averaged 0.02679 microseconds over the five tests, while the virtual getter averaged 0.151478 microseconds. The actual getter performed 5.65 times faster than the virtual getter.
It's important to note as well that further logic should be added to the virtual getters and setters in order to limit access to properties that are truly protected or private. This would introduce additional overhead and execution time, giving actual getters and setters an even greate advantage.
These results shouldn't deter anyone from using virtual getters and setters. To put these results into perspective, 1000 retrievals using a virtual getter still only adds up to an average of roughly 151 microseconds or 0.0151 seconds. In other words, it may not introduce a large bottleneck in your application, unless your application is running in a high-demand environment.
Comments