0

I have a large data-set that I'm checking the contents of; I do this validation while creating an internal array of the data; to avoid looping over the array again later, I would like the validation to change the contents of the array. Now the problem is that I'm calling the validation routines through call_user_func and this seems to pose some problems with passing by reference. Or maybe I'm doing something else wrong.

Here's a stripped down example:

public function index( )
{
    $arr = array( 
        array('a' => 'aap', 'n' => 'noot', 'm' => 'mies'), 
        array('a' => 'ding', 'b' => 'flof', 'c' => 'bips'), 
        array( 'd' => 'do', 'e' => 're', 'c' => 'mi') 
    );

    $func = array( $this, '_user_func' );

    $errors = 0;
    $new_arr = array();
    foreach ($arr as $key => &$value) {
        $new_arr[$key] = &$value; // Simulate production-code manipulation
        //if ( !$this->_do_callback($func, $new_arr[$key], $key) ) $errors++; // No exception but array not modified afterwards
        if ( !call_user_func( $func, $new_arr[$key], $key ) ) $errors++; // Exception: Parameter 1 to TestRef::user_func() expected to be a reference, value given
    }
    unset($value);
    var_dump($new_arr);
    print_r('Errors: '.$errors);
}

private function _do_callback( $func, array &$row, $row_id )
{
    if ( is_callable( $func ) )
    {
        return call_user_func( $func, $row, $row_id );
    }
    else
    {
        throw new Exception( "Error doing callback. Callback empty or not a callable function." );
    }
}

private function _user_func( &$arr, $index = 0 )
{
    // "Validation" routine
    foreach ($arr as $key => &$value) {
        if ($key == 'b') return FALSE; // Simulate validation error for error count
        $arr[$key] = 'replaced';
    }
    unset($value);
    //var_dump($arr); // Works!
    return TRUE;
}
Alex
  • 631
  • 1
  • 8
  • 33

3 Answers3

1

Either:

Try changing your foreach loop into this:

foreach ($arr as $key => &$value) {
    $this->_do_callback($func, $value, $key); // No exception but array not modified afterwards
    //call_user_func( $func, $value, $key ); // Exception: Parameter 1 to TestRef::user_func() expected to be a reference, value given
}

unset($value); // avoid memory leak

Or:

Wrap the variable in an array prior to invoking call_user_func.

Oscar Broman
  • 1,109
  • 8
  • 19
  • I tried your first suggestion; it is another way to hide the exception thrown with direct call_user_func calling; but the resulting array still has the original values. – Alex Sep 10 '12 at 10:52
  • What would wrapping the variable in an array do? (blindly doing it as you suggested yields no results :) ) – Alex Sep 10 '12 at 10:53
  • 1
    It preserves the pointer properly. Excuse the mess, but I think this should do it: http://pastebin.com/VnKNKZGD – Oscar Broman Sep 10 '12 at 10:54
  • Hm, interesting. But it still throws "Parameter 1 to TestRef::_user_func() expected to be a reference, value given" – Alex Sep 10 '12 at 10:59
  • Passing it directly to the function *does* seem to work: call_user_func( $func, array(&$value), $key ); It is slightly irritating to have to do "$arr = &$arr[0];" at the start of the callee now, though.. – Alex Sep 10 '12 at 11:01
  • What PHP version do you have? I didn't get a warning. – Oscar Broman Sep 10 '12 at 11:02
1

i think you're trying to redefine an existing php function which is array_walk. And especially in your case, you'll need array_walk_recursive.

Here's a rewritten (simplified ?) version of your code.

    public function index( )
    {
        $arr = array( 
            array('a' => 'aap', 'n' => 'noot', 'm' => 'mies'), 
            array('a' => 'ding', 'b' => 'flof', 'c' => 'bips'), 
            array( 'd' => 'do', 'e' => 're', 'c' => 'mi') 
        );

        $func = array( $this, '_user_func' );

        var_dump($arr); // BEFORE WALK
        array_walk_recursive($arr, $func);
        var_dump($arr); // AFTER WALK
    }

    /**
     *  Does something to a row from an array (notice the reference)
     */
    private function _user_func( &$rowValue, $rowIndex )
    {
            $rowValue = 'replaced';
    }

You can see this code in action here --> http://ideone.com/LcZKo

Giorgiolino
  • 165
  • 10
  • Nice! I even dreamed up the index part the same way.. :) For my case array_walk would be more appropriete I think (instead of recursive), because I operate on each row, not each value of each row. I'll see if I can translate this to the real app. – Alex Sep 10 '12 at 11:23
  • The problem with this one is that in the production code, a lot of stuff happens in the outer loop before even having a $row to do the callback with. array_walk would only be possible after gathering $arr. But this would mean iterating over the whole set again, which I'm trying to avoid. So indeed: I am trying to rewrite array_walk, sort-of :) How are the PHP guys doing the callback? – Alex Sep 10 '12 at 11:44
  • Additionally, if a callback fails, I just want to count the error and continue on to the next row. I think if I return false in the callee now, the whole array_walk would stop? – Alex Sep 10 '12 at 12:48
  • I think you're asking many follow-up questions here that deserve a research on this site or even another question. – Giorgiolino Sep 11 '12 at 06:58
  • 1
    Callbacks --> http://stackoverflow.com/questions/48947/how-do-i-implement-a-callback-in-php By the way, some aspect remain unclear for me : - "if a callback fails" : what do you mean by "fail" ? (error, exception,...) ? Where does the failure come from ? Who/what raise it ? - "If i return fals in the callee" : since you're working with references you don't have return values ? Where/how do you get them ? As you see, you'd better rephrase your question or elaborate more imho. – Giorgiolino Sep 11 '12 at 07:04
  • I updated the question to more closely follow the issues present in production code. Note: The reason I return FALSE from the callback function is that I want to stop when, say, 100 errors have occurred. So false counts as an error and a counter is upped. The actual logging of the error message etc. is done in another lib. – Alex Sep 11 '12 at 12:41
  • Might the "function ... use" closure syntax help us here? (http://stackoverflow.com/questions/1065188/in-php-5-3-0-what-is-the-function-use-identifier-should-a-sane-programmer-us) – Alex Sep 11 '12 at 19:51
  • 1
    I don't really see how the "use" keyword could help here (which does not mean it's useless). Based on what you said before, i understand that you need to run some code against some array elements as long as some condition is met. So array_walk is not well suited since you can not exit from the "walk". Here's an attempt to do what what you seem to be looking for --> http://ideone.com/41gmQ Maybe you could edit this try to suit your need, thus clarifying what you really need. – Giorgiolino Sep 11 '12 at 20:52
  • Giorgiolino: Thanks for the suggestions. The problem here, though, is that the function being called is actually in another class, so fundamentally it's still about passing references to call_user_func. I moved some code around to make array_walk possible and it seems to be performing fairly well. The max_error issue i worked around by passing error_count and max_errors to the handler and just returning at the top there if error_count exceeds max_errors. FYI: The main array is a PHPExcel loaded excel-sheet. I'm trying to optimize it for 5M cell sheets.. – Alex Sep 12 '12 at 11:11
0

Have you tried it like this:?

foreach ($arr as $key => &$value) {
        $this->_do_callback($func, $value, $key); // No exception but array not modified afterwards
        //call_user_func( $func, $value, $key ); // Exception: Parameter 1 to TestRef::user_func() expected to be a reference, value given
    }
JvdBerg
  • 21,777
  • 8
  • 38
  • 55
  • Yes, this is another way to hide the exception thrown with direct call_user_func calling; but the resulting array still has the original values. – Alex Sep 10 '12 at 10:44