Thursday, May 5, 2011

Is an 'invoke'-style callback possible in PHP5?

PHP functions such as 'array_map' take a callback, which can be a simple function or a class or object method:
$array2 = array_map('myFunc', $array);
or
$array2 = array_map(array($object, 'myMethod'), $array);

But is there a syntax to pass a method which is bound within the iteration to the current object (like 'invoke' in Prototype.js)? So that I could say

$array2 = array_map('myMethod', $array);

with the effect of

foreach($array as $obj) $array2[] = $obj->myMethod();

Obviously I can use this form, or I can write a wrapper function to make the call, and even do that inline. But since 'myMethod' is already a method it seems to be going round the houses to have to do one of these.

From stackoverflow
  • Not currently. When php 5.3 comes out, you could use the following syntax:

    $array2 = array_map(function($obj) { return $obj->myMethod(); }, $array);
    
    Colin Fine : Which is longer and less obvious than my foreach method. Thanks.
    troelskn : It's ugly alright, but at least it doesn't use temp variables. If you come from a function-oriented language, such as Ruby or lisp, it appears more intuitive.
  • Basically, no. There is no special syntax to make this any easier.

    I can think of a fancier way of doing this in PHP 5.3, seeing as there's always more than one way to do things in PHP, but I'd say it wouldn't necessarily be better than your foreach example:

    $x = array_reduce(
        $array_of_objects, 
        function($val, $obj) { $val = array_merge($val, $obj->myMethod()); return $val; },
        array() 
    );
    

    Just go with your foreach :)

    soulmerge : The third parameter of array_reduce() must be an integer, so this solution wouldn't work. $ReasonsToHatePhp++;
    MathieuK : Actually, that appears to be a documentation error. The prototype in ext/standard/array.c:4015 defines it as 'mixed'.
    soulmerge : Try running the code. You will notice, that you will receive 0 in the first recursion - and not an empty array.
  • function obj_array_map($method, $arr_of_objects) {
        $out = array();
        $args = array_slice(func_get_args(), 2);
        foreach ($arr_of_objects as $key => $obj) {
            $out[$key] = call_user_func_array(Array($obj, $method), $args);
        }
        return $out;
    }
    
    // this code
    $a = Array($obj1, $obj2);
    obj_array_map('method', $a, 1, 2, 3);
    
    // results in the calls:
    $obj1->method(1, 2, 3);
    $obj2->method(1, 2, 3);
    
  • <?php
    
    // $obj->$method(); works, where $method is a string containing the name of the
    // real method
    function array_map_obj($method, $array) {
        $out = array();
    
        foreach ($array as $key => $obj)
            $out[$key] = $obj->$method();
    
        return $out;    
    }
    
    // seems to work ...
    
    class Foo {
        private $y = 0;
        public function __construct($x) {
            $this->y = $x;
        }
        public function bar() {
            return $this->y*2;
        }
    }
    
    $objs = array();
    for ($i=0; $i<20; $i++)
        $objs[] = new Foo($i);
    
    $res = array_map_obj('bar', $objs);
    
    var_dump($res);
    
    ?>
    

    voila!

  • This is a bit of a silly answer, but you could subclass ArrayObject and use that instead of a normal array:

    <?php
    
    class ArrayTest extends ArrayObject {
        public function invokeMethod() {
            $result = array();
            $args = func_get_args();
            $method = array_shift($args);
            foreach ($this as $obj) {
                $result[] = call_user_func_array(array($obj, $method), $args);
            }
            return $result;
        }
    }
    
    //example class to use
    class a { 
        private $a;
        public function __construct($a) { 
            $this->a = $a; 
        }
    
        public function multiply($n) {
            return $this->a * $n;
        }
     }
    
    //use ArrayTest instance instead of array
    $array = new ArrayTest();
    $array[] = new a(1);
    $array[] = new a(2);
    $array[] = new a(3);
    
    print_r($array->invokeMethod('multiply', 2));
    

    Outputs this:

    Array
    (
        [0] => 2
        [1] => 4
        [2] => 6
    )
    
  • I would use create_function() to ... well ... create a temporary function for array_map while waiting for PHP 5.3

    $func = create_function('$o', '$o->myMethod();');
    array_map($func, $objects);
    
    Colin Fine : I can't really see any point in using create_function and not use it inline. If you are going to assign it to the variable $func, you might as well create a named function.
    soulmerge : Use it inline, then. I don't like my source code growing too long (horizontally). Besides, storing it in a variable makes it clear to anyone looking at the source code that this function is used nowhere else.

0 comments:

Post a Comment