Not sure how to title this exactly. Whilst digging into the Laravel 4 classes to see how Facades work, I stumbled upon this:
Illuminate\Support\Facades\Facades.php@__callStatic
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
switch (count($args))
{
case 0:
return $instance->$method();
case 1:
return $instance->$method($args[0]);
case 2:
return $instance->$method($args[0], $args[1]);
case 3:
return $instance->$method($args[0], $args[1], $args[2]);
case 4:
return $instance->$method($args[0], $args[1], $args[2], $args[3]);
default:
return call_user_func_array(array($instance, $method), $args);
}
}
Now from what I can tell this method calls any given method of the class the Facade references and passes the arguments along. I could be wrong but thats my understanding so far.
The part that really bugs me is the switch.
Why are cases 0 through to 4 needed when the default case would work regardless.
Even if case 0 kind of makes sense if there is no arguments, why have case 1-4 and not just continue to case 10 for example. Is there a reasonable argument for this or is it just a case of premature optimisation?
You are right, calling call_user_func_array();
works perfectly without using switch
statement. But, according to this benchmark, It seems terribly slow:
function html($value)
{
return htmlentities($value);
}
name : diff : total : description
native : 0.614219 : 0.613295 : htmlentities($value)
literal_func : 0.745537 : 1.346594 : html($value)
variable_func : 0.826048 : 2.162376 : $func($value)
literal_method : 0.957708 : 3.127519 : $object->html($value)
variable_method : 0.840837 : 3.970290 : $object->$func($value)
call_func : 1.006599 : 4.993930 : call_user_func($func, $value)
call_object : 1.193323 : 6.215677 : call_user_func((array($object, $func), $value)
cufa_func : 1.232891 : 7.320287 : call_user_func_array($func, array($value))
cufa_object : 1.309725 : 8.769755 : call_user_func_array((array($object, $func), array($value)
So basically this only becomes an issue when you use call_user_func_array()
a lot of times (this is the case in Laravel). That's why they use the switch
statement.
I suspect that it is a micro-optimization. I would also suspect that the majority of these static calls to facades would require 4 or fewer arguments, in which case the majority of static calls would not jump into the default case.
I was able to find this user-submitted quote in the manual entry on call_user_func_array
from 'noone at example dot com':
For those of you that have to consider performance: it takes about 3 times as long to call the function this way than via a straight statement, so whenever it is feasible to avoid this method it's a wise idea to do so.
Doing a very simple benchmark also seems to confirm the validity of this. In the results, 1 is calling a method on an instance directly, 2 is calling a variable method name on an instance, and 3 is using call_user_func_array
, and output time is in seconds. Number of iterations for each approach is 1,000,000.
$ php test.php
(1) total: 0.51281404495239
(2) total: 0.51285219192505
(3) total: 1.1298811435699
$ php test.php
(1) total: 0.49811697006226
(2) total: 0.5209321975708
(3) total: 1.1204349994659
$ php test.php
(1) total: 0.48825788497925
(2) total: 0.51465392112732
(3) total: 1.156769990921
The results above indicate that avoiding call_user_func_array
could speed up calls to static facade methods at least by around a factor of 2, unless the static method has more than 4 arguments.
As to why the cutoff number of 4 parameters was chosen, only Taylor knows. That method has been (mostly) unchanged since Laravel 4.0's first commit, and I suspect it's somewhat arbitrary.
Because $instance->$method()
is way faster than call_user_func_array
.
Given that this piece of code can be called many many times in a single cycle, it makes sense to optimize it as much as possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With