Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP Laravel Facade __callStatic argument list

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?

like image 902
Ozzy Avatar asked Oct 29 '14 14:10

Ozzy


3 Answers

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.

like image 41
Razor Avatar answered Oct 24 '22 20:10

Razor


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.

like image 124
Jeff Lambert Avatar answered Oct 24 '22 20:10

Jeff Lambert


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.

like image 42
Joseph Silber Avatar answered Oct 24 '22 19:10

Joseph Silber