I was wondering about what the best practices are when it comes to call a class method using the above functions to fill up a method call dynamically using an array!
What advantages and disadvantages do I have? I mean, it seems the RefelectionMethod + invokeArgs option is up to 40% faster than the call_user_funcion in certain conditions… but do I miss something?
thanks.
As requested i will add a little benchmark of my scenario where i needed refereces to be passed... i know its a very specific case and that this is not stricly related to the question above!
class foo { public function bar(&$a, &$b, &$c) { /* */ } }
$t1 = microtime(true);
$arr = array(1,2,3);
$foo = new foo;
$rfl = new ReflectionMethod('foo', 'bar');
for ($i=0; $i < 10000; ++$i)
{
$rfl->invokeArgs($foo, $arr);
}
$t2 = microtime(true);
echo sprintf("\nElapsed reflectionmethod : %f", $t2 - $t1);
$t1 = microtime(true);
$arr = array(1,2,3);
$foo = new foo;
for ($i=0; $i < 10000; ++$i)
{
foreach( $arr as $k => $v ) $ref[$k] = &$arr[$k];
call_user_func_array( array($foo, 'bar'), $arr);
}
$t2 = microtime(true);
echo sprintf("\nElapsed calluserfuncarray : %f", $t2 - $t1);
the result
Elapsed reflectionmethod : 0.025099
Elapsed calluserfuncarray : 0.051189
I really would just like to know when its better to use one versus the other, and why! its not strictly related to speed though!
Not sure where you got your test results but RefelectionMethod + invokeArgs option is up to 40% faster than the call_user_funcion seems far fetched it can only be possible with single instance multiple invoke
Simple Benchmark
set_time_limit(0);
echo "<pre>";
class Foo {
public function bar($arg1, $arg2) {
}
}
$globalRefection = new ReflectionMethod('Foo', 'bar');
$globalFoo = new Foo();
// Using call_user_func_array
function m1($args) {
$foo = new Foo();
call_user_func_array(array($foo,"bar"), $args);
}
// Using ReflectionMethod:invoke
function m2($args) {
$foo = new ReflectionMethod('Foo', 'bar');
$foo->invoke(new Foo(), $args[0], $args[1]);
}
// Using ReflectionMethod:invokeArgs
function m3($args) {
$foo = new ReflectionMethod('Foo', 'bar');
$foo->invokeArgs(new Foo(), $args);
}
// Using Global Reflection
function m4($args) {
global $globalRefection;
$globalRefection->invokeArgs(new Foo(), $args);
}
// Using Global Reflection + Glbal foo
function m5($args) {
global $globalRefection, $globalFoo;
$globalRefection->invokeArgs($globalFoo, $args);
}
$result = array('m1' => 0,'m2' => 0,'m3' => 0,'m4' => 0,'m5' => 0);
$args = array("arg1","arg2");
for($i = 0; $i < 10000; ++ $i) {
foreach ( array_keys($result) as $key ) {
$alpha = microtime(true);
$key($args);
$result[$key] += microtime(true) - $alpha;
}
}
echo '<pre>';
echo "Single Run\n";
print_r($result);
echo '</pre>';
Output
Single Run
Array
(
[m1] => 0.018314599990845 <----- call_user_func_array
[m2] => 0.024132013320923 <----- ReflectionMethod:invoke
[m3] => 0.021934270858765 <----- ReflectionMethod:invokeArgs
[m4] => 0.012894868850708 <----- Global Relection
[m5] => 0.01132345199585 <----- Global Reflection + Global Foo
)
See Live Action
Related
In PHP we can do things like these:
Class Example {
...
}
$example = 'Example';
$object = new $example();
Or the use of variable variables:
$hour = 18;
$greets = array('Good morning','Good afternoon','Good evening');
$values = array(13,21,23);//people is sleeping at 23PM, so they don't greet.
$n = count($values);
$greet = 'greets';
for($i=0;$i<$n;$i++){
if($hour < $values[$i]){
echo 'hello, '.${$greet}[$i];
break;
}
}
And others..
I wonder if it would be possible to access directly to a specific index of a multidimensional array in a similar way. Something like:
$array = array(...); //multidimensional array.
$position = '[0][4][3]';
print_r($array$position);
Thanks in advance.
UPDATE
I'm so sorry because I finished my question in a wrong way.
I need to set the multimesional array and add a value. i.e:
$array$position = $data;
You could implement it yourself with a custom function:
function getValueFromMultiDimensionalArray( array $array, string $key )
{
$keys = explode('][', $key);
$value = $array;
foreach ($keys as $theKey) {
// remove the opening or closing bracket if present
$theKey = str_replace([ '[', ']' ], '', $theKey);
if (!isset($value[$theKey])) {
return null;
}
$value = $value[$theKey];
}
return $value;
}
You can define path as dot separated , check the following solution
function getValueByKey($a,$p){
$c = $a;
foreach(explode('.',$p) as $v){
if(!array_key_exists($v, $c)) return null;
$c = $c[$v];
}
return $c;
}
You can use this function as
$path = '1.2.3.0';
$indexValue = getValueByKey($array, $path);
Nope, this is not possible.
The only thing you can do is to implement ArrayAccess interface, which allows to access instances with [] operator. But you will have to define the logic yourself.
class MyClass implements ArrayAccess
{
...
}
$x = new MyClass([0=>[4=>[3=>'hello world']]]);
$position = '[0][4][3]';
echo $x[$position]; //hello world
I want to loop over an array of anonymous functions in php and call each one like this:
for ($i = 0; $i < count($headingArray); $i++) {
echo $headingArray[$i];
$callBackFunction = $functionArray[$i]($file);
echo $callBackFunction;
echo $divider;
}
The idea is that it will display the heading and then the appropriate data that is returned from each function underneath.
I am getting Fatal error: Uncaught Error: Function name must be a string.
Is there any way of using the for loop index $i to call each one or can you only explicitly pass the name of the function when accessing it from the array?
<?php
$a = function($n) {
return 'a';
};
$b = function($n) {
return 'b';
};
$functions = [$a, $b];
foreach($functions as $func) {
echo $func('foo'), "\n";
}
Output:
a
b
Headings example (where the anonymous functions and headings share like keys:
<?php
$functions =
[
function($n) {
return 'a';
},
function($n) {
return 'b';
}
];
$headings =
[
'a heading',
'b heading'
];
foreach($functions as $k => $func) {
echo $headings[$k], "\n", $func('foo'), "\n";
}
Output:
a heading
a
b heading
b
Is there any chance that I can deduce PHP Closure parameters type information? Consider this example:
<?php
$foo = function(array $args)
{
echo $args['a'] . ' ' . $args['b'];
};
$bar = function($a, $b)
{
echo $a . ' ' . $b;
};
$closure = /* some condition */ $foo : $bar;
if(/* $closure accepts array? */)
{
call_user_func($closure, ['a' => 5, 'b' => 10]);
}
else
{
call_user_func($closure, 5, 10);
}
?>
I want to leave some freedom for user so he or she could decide which way is better to define a Closure that will be registered in my dispatcher - will it accept parameters in associative array or directly as Closure parameters. So, dispatcher need to deduce parameters of the passed Closure to determine which way should it call this Closure. Any ideas?
Use reflection, if you need to make decisions, based on code structure. In your case ReflectionFunction and ReflectionParameter are your friends.
<?php
header('Content-Type: text/plain; charset=utf-8');
$func = function($a, $b){ echo implode(' ', func_get_args()); };
$closure = $func;
$reflection = new ReflectionFunction($closure);
$arguments = $reflection->getParameters();
if($arguments && $arguments[0]->isArray()){
echo 'Giving array. Result: ';
call_user_func($closure, ['a' => 5, 'b' => 10]);
} else {
echo 'Giving individuals. Result: ';
call_user_func($closure, 5, 10);
}
?>
Output:
Giving individuals. Result: 5 10
Change definition to test:
$func = function(array $a){ echo implode(' ', $a); };
Output:
Giving array. Result: 5 10
It would be much easier to have your function be able to accept different types of input.
For instance, in this case:
$foo = function() {
$args = func_get_args();
if( is_array($args[0])) $args = $args[0];
echo $args[0]." ".$args[1];
}
So, I've written some rather convoluted 'functional' PHP code to perform folding on an array. Don't worry, I won't use it anywhere. The problem is, PHP's 'each' function only seems to go as far as the end of an array as it is statically (actually, see bottom) declared.
// declare some arrays to fold with
$six = array("_1_","_2_","_3_","_4_","_5_","_6_");
// note: $ns = range(0,100) won't work at all--lazy evaluation?
$ns = array(1,2,3,4,5,6,7,8);
$ns[8] = 9; // this item is included
// add ten more elements to $ns. each can't find these
for($i=0; $i<10; ++$i)
$ns[] = $i;
// create a copy to see if it fixes 'each' problem
$ms = $ns;
$ms[0] = 3; // Just making sure it's actually a copy
$f = function( $a, $b ) { return $a . $b; };
$pls = function( $a, $b ) { return $a + $b; };
function fold_tr( &$a, $f )
{
$g = function ( $accum, &$a, $f ) use (&$g)
{
list($dummy,$n) = each($a);
if($n)
{
return $g($f($accum,$n),$a,$f);
}
else
{
return $accum;
}
};
reset($a);
return $g( NULL, $a, $f );
}
echo "<p>".fold_tr( $six, $f )."</p>"; // as expected: _1__2__3__4__5__6_
echo "<p>".fold_tr( $ns, $pls )."</p>"; // 45 = sum(1..9)
echo "<p>".fold_tr( $ms, $pls )."</p>"; // 47 = 3 + sum(2..9)
I honestly have no clue how each maintains its state; it seems vestigial at best, since there are better (non-magical) mechanisms in the language for iterating through a list, but does anyone know why it would register items added to an array using $a[$index] = value but not '$a[] = value`? Thanks in advance any insight on this behavior.
Your loop is exiting early thanks to PHP's weak typing:
if($n)
{
return $g($f($accum,$n),$a,$f);
}
else
{
return $accum;
}
when $n is 0 (e.g. $ns[9]), the condition will fail and your loop will terminate. Fix with the following:
if($n !== null)
So I'm trying to implement an Aspect-Oriented Design into my architecture using debug_backtrace and PHP reflection. The design works but I decided to see just how badly it impacts performance so I wrote up the following profiling test. The interesting thing is that when the Advisable and NonAdvisable methods do nothing, the impact is about 5 times that for using an advisable method versus using a non-advisable method, but when I increase the complexity of each method (here by increasing the number of iterations to 30 or more), advisable methods perform begin to perform better and continue to increase as the complexity increases.
Base class:
abstract class Advisable {
private static $reflections = array();
protected static $executions = 25;
protected static function advise()
{
$backtrace = debug_backtrace();
$method_trace = $backtrace[1];
$object = $method_trace['object'];
$function = $method_trace['function'];
$args = $method_trace['args'];
$class = get_called_class();
// We'll introduce this later
$before = array();
$around = array();
$after = array();
$method_info = array(
'args' => $args,
'object' => $object,
'class' => $class,
'method' => $function,
'around_queue' => $around
);
array_unshift($args, $method_info);
foreach ($before as $advice)
{
call_user_func_array($advice, $args);
}
$result = self::get_advice($method_info);
foreach ($after as $advice)
{
call_user_func_array($advice, $args);
}
return $result;
}
public static function get_advice($calling_info)
{
if ($calling_info['around_queue'])
{
$around = array_shift($calling_info['around_queue']);
if ($around)
{
// a method exists in the queue
return call_user_func_array($around, array_merge(array($calling_info), $calling_info['args']));
}
}
$object = $calling_info['object'];
$method = $calling_info['method'];
$class = $calling_info['class'];
if ($object)
{
return null; // THIS IS THE OFFENDING LINE
// this is a class method
if (isset(self::$reflections[$class][$method]))
{
$parent = self::$reflections[$class][$method];
}
else
{
$parent = new ReflectionMethod('_'.$class, $method);
if (!isset(self::$reflections[$class]))
{
self::$reflections[$class] = array();
}
self::$reflections[$class][$method] = $parent;
}
return $parent->invokeArgs($object, $calling_info['args']);
}
// this is a static method
return call_user_func_array(get_parent_class($class).'::'.$method, $calling_info['args']);
}
}
An implemented class:
abstract class _A extends Advisable
{
public function Advisable()
{
$doing_stuff = '';
for ($i = 0; $i < self::$executions; $i++)
{
$doing_stuff .= '.';
}
return $doing_stuff;
}
public function NonAdvisable()
{
$doing_stuff = '';
for ($i = 0; $i < self::$executions; $i++)
{
$doing_stuff .= '.';
}
return $doing_stuff;
}
}
class A extends _A
{
public function Advisable()
{
return self::advise();
}
}
And profile the methods:
$a = new A();
$start_time = microtime(true);
$executions = 1000000;
for ($i = 0; $i < $executions; $i++)
{
$a->Advisable();
}
$advisable_execution_time = microtime(true) - $start_time;
$start_time = microtime(true);
for ($i = 0; $i < $executions; $i++)
{
$a->NonAdvisable();
}
$non_advisable_execution_time = microtime(true) - $start_time;
echo 'Ratio: '.$advisable_execution_time/$non_advisable_execution_time.'<br />';
echo 'Advisable: '.$advisable_execution_time.'<br />';
echo 'Non-Advisable: '.$non_advisable_execution_time.'<br />';
echo 'Overhead: '.($advisable_execution_time - $non_advisable_execution_time);
If I run this test with the complexity at 100 (A::executions = 100), I get the following:
Ratio: 0.289029437803
Advisable: 7.08797502518
Non-Advisable: 24.5233671665
Overhead: -17.4353921413
Any ideas?
You're skipping all of the iterations when you call A's Advisable method... you're overwriting it with one single call to the inherited advise() method. So, when you add iterations, you're only adding them to the NonAdvisable() call.
method overhead should apply to PHP as well as to Java i guess - "the actual method that is called is determined at run time" => the overhead for shadowed Advisible method is bigger
but it would be O(1) instead of Non-Advisable's O(n)
Sorry for the bother, I just found the line return null; in the get_advice method before the parent method gets called. I hate to answer my own question but it's not really worth someone else searching for it.