Blog Posts

FOTD: ReflectionClass()->newInstanceArgs($args) is "slow"

(FOTD = Finding of the day :))

For okapi we needed to have a function which loads any class with any number of arguments. This is not so easy in PHP as it looks like and the way to do this until 5.1.3 was something like:

function init($name,$init) {
    switch(count($init)) {
        case 0:
          return new $name();
        case 1:
          return new $name($init[0]);
        case 2:
          return new $name($init[0],$init[1]);
        case 3:
          return new $name($init[0],$init[1],$init[2]);
        case 4: 
          //etc...
     }
}

Looks ugly, but works, as long as you have as many "case" statements as you have arguments

Since 5.1.3 you can use the reflection extension to do this much nicer

function init($name,$init) {
    if (count($init) == 0) {
        return new $name();
    } else {
        $classObj = new ReflectionClass($name);
        return $classObj->newInstanceArgs($init);
    }
}

But as my gut said that ReflectionClass is not the fastest thing on earth, I made some benchmarks and indeed:

PHP 5.2 with switch    :  9.6 
PHP 5.2 with reflection: 16.6
PHP 5.3 with switch    :  3.3
PHP 5.3 with reflection:  6.2

The ugly switch statement is almost twice as fast as the one with the reflection class. But even more surprising (to me) was that both are three times faster in PHP 5.3. I'm now even more looking forward to the final release of PHP 5.3.0 :)

BTW, we decided nevertheless for the reflection approach, as it is way less ugly and the performance difference will be negligible. And the unit for the figures in the benchmark is not really important, the relative comparison is what's interesting.

Related Entries:
- LaraconEU 2014 - Empowering the PHP people
- Discussions and Pizza at PHPDay Italy
- Of HHVM, Hack and the future of PHP
- RESTing with Symfony2
- Content storage done right

About the author

Comments [14]

harald, 18.09.2008 17:56 CEST

i have a similar method in my framework. it works like a charm. not only is the switch statement ugly, it will also fail, if you accidently implement something, which requires even more parameters.

of course you could just write down 100 cases, but this really would be a candidate for the daily WTF!? ;)

Rob Wultsch, 18.09.2008 18:59 CEST

eval may be evil, but it sure can be useful...:

<?php
$name = 'bar';
$init[] = 'baz';
$init[] = 'booze';

if($init)
$vars = '"'.implode('","',$init).'"';

$eval = "$ob = new $name($vars); ";

eval($eval);
die(print_r($ob,1));

class bar{
public function __construct($i='not i',$j='not j',$k='not k'){
echo "$i
$j
$k
";
}
}

Chris, 19.09.2008 03:10 CEST

What about using call_user_func_array ?

return call_user_func_array(array($name), $vars);

Or something to that effect :)

Yoan, 19.09.2008 14:11 CEST

@Rob: eval is evil and your example will only work with strings, bad...

@Chris call_user_func_array works with static method well, not really with __construct.

won't do what you want.

I ended up with something similar to what we do in JS ;-)

http://gist.github.com/11582#LID24

Of course, you won't be able to write normal PHP classes anymore, but maybe this syntax: new Object(array(a => 1, b => 2)); can be interesting to have. Yes, it's Pythonic ;-)

Robin Orheden, 19.09.2008 17:53 CEST

I also use reflection for creating instances dynamicly in my OOP based framework. Works well, and the time spent is in end probably worth it.

@Rob: Why not create a function for that? That also supports objects...

function CreateInstance($name, Array $argv)
{
$args = "$argv[" . implode("], $argv[", array_keys($argv)) . "]";
return class_exists($name) ? eval("return new {$name}({$args});") : false;
}

Not tested, but should get the job done.. A little more securly too ;-)

Robin Orheden, 19.09.2008 18:01 CEST

Really nice that this blog removes the backward slashes in the code(sarcastic). Function will most likely _not_ function without them since vars will be evaluated directly instead of (as desired) when passed through Eval().

function CreateInstance($name, Array $argv)
{
$args = "(backward slash)$argv[" . implode("], (backward slash)$argv[", array_keys($argv)) . "]";
return class_exists($name) ? eval("return new {$name}({$args});") : false;
}

So for those geniouses out there.. Replace "(backward slash)" with a real backward slash..

Robin Orheden, 19.09.2008 18:11 CEST

http://gist.github.com/11613

F- me for being so nice ;)

Yoan, 22.09.2008 15:12 CEST

@Robin:

CreateInstance("myClass", array(null, new stdClass()));

fail.

Chris, 24.09.2008 05:33 CEST

I thought call_user_func_array would use the constructor if you didn't supply a proper array but it complains.

However saying it only works with static methods is wrong.

http://pastebin.com/f293f4985

Robin, 24.09.2008 12:01 CEST

@Yoan: Can't see how the function would fail.

Yoan, 24.09.2008 13:20 CEST

> However saying it only works with static methods is wrong.

Yep, right. Double right actually, all my apologies. What about the perfs of eval?

Yoan, 24.09.2008 13:40 CEST

Gist is updated (http://gist.github.com/11582) and reflection looks worser than eval. Anyone to run this upon PHP 5.3 ?

Cheers,

Yoan, 24.09.2008 13:42 CEST

Not worse, just a little bit better. Sorry (now I stop flooding here)

Enrico Stahn, 21.02.2013 07:17 CEST

Hi Guys,

After refactoring ReflectionClass::newInstanceArg() i found this Blog-Post. Check out this similar version which works not only with string arguments:

https://gist.github.com/estahn/5002290

Cheers
Enrico

Add a comment

Your email adress will never be published. Comment spam will be deleted!