Paul Harris
Paul Harris

Reputation: 105

How to pass multiple variables to and from a callback function in PHP?

To make my database access code more robust, I have some code in a loop that retries the database operation if there is a transient failure (eg a deadlock exception). It is working quite well and I am using that retry loop now in many places. Here is that code:

Original Code

$try=5;
while ($try-- and !$bDbUpdated){
    try {
        $notifications = selectNotifications($db, $operation);
        $content = sendNotifications($db, $operation, $notifications, $domain);

        $bDbUpdated = true;

    } catch (Exception $e) {
        if ($try> 0) {
            usleep( 100 * 1000 ); // sleep for 100 milliseconds
        } else {
            throw $e; 
        }
    }
}

But now I want to put the retry code in a function for code re-use.

What I have tried is wrapping the retry loop in a function, passing it a callback function (which is the database access code).

New Code

Here is this retry function:

function retryTransaction($try, $db, &$operation, $domain, $callback){
    $bDbUpdated = false;
    $return;
    while ($try-- and !$bDbUpdated){
        try {
            $return = $callback($db,$operation,$domain);
            $bDbUpdated = true;
        } catch (Exception $e) {
            if ($try> 0) {
                usleep( 100 * 1000 ); // sleep for 100 milliseconds
            } else {
                throw $e; 
            }
        }
    }
    return $return;
} 

And this is how I call it:

retryTransaction(5, $db, $operation, $domain, function($db, $operation, $domain){
    $notifications = selectNotifications($db, $operation);
    $content = sendNotifications($db, $operation, $notifications, $domain);
    return $content;
} );

I am able to pass multiple variables in (5,$db, $operation, $domain) and return one variable ($content). And the code works. But I also want to return the number of retries, so I need to return multiple values ($content and $try).

What would be the cleanest way to make accessible multiple variables both in and out, to give the maximum benefit of code-reuse to this retryTransaction function?

Eg, Is passing variables in as argument the only option? Would declaring the variables as global be better? Is there a better structure I should consider?

Edit: the Use clause

I discovered the use clause: it makes variables in the parent scope accessible to the callback function. I call the function like this:

$try = 5;
$content = retryTransaction($try,function() use ($db,&$operation,$domain){
    $notifications = selectNotifications($db, $operation);
    $content = sendNotifications($db, $operation, $notifications, $domain);
    return $content;
} );

And the retryTransaction function doesn't need to be have those variables as arguments:

function retryTransaction(&$try,$callback){
    $success = false;
    $return;
    $max = $try;
    $try=0;
    while ($try < $max and !$success){
        try {
            $return = $callback();
            $success = true;
        } catch (Exception $e) {
            if ($try < $max) {
                usleep( 100 * 1000 ); // sleep for 100 milliseconds
            } else {
                throw $e; // log the exception
            }
        }
        $try++;
    }
    return $return;
} 

This maximises code re-use because the retryTransaction function can be used in different situations requiring different parent scope variables.

The number of retries is returned in the $try argument which is passed by reference. This is an alternative solution to the returning it in an array return value.

Thanks for all the suggestions so far. Still open to further suggestions on improving re-usability.

Upvotes: 0

Views: 1498

Answers (2)

weegee
weegee

Reputation: 3399

Return your data in the form of an array. You want to get the number of tries and the content variable. So your code becomes like this

function retryTransaction($try, $db, &$operation, $domain, $callback){
    $bDbUpdated = false;
    $return;
    while ($try-- and !$bDbUpdated){
        try {
            $return = $callback($db, $operation, $domain, $try); // pass try here
            $bDbUpdated = true;
        } catch (Exception $e) {
            if ($try> 0) {
                usleep( 100 * 1000 ); // sleep for 100 milliseconds
            } else {
                throw $e; 
            }
        }
    }
    // return $return; don't return the value here
} 

And call it like

$data = retryTransaction(5, $db, $operation, $domain, function($db, $operation, $domain, $try){
    $notifications = selectNotifications($db, $operation);
    $content = sendNotifications($db, $operation, $notifications, $domain);
    return [$content, $try];
});
$contentfromData = $data[0];
$numberoftries = $data[1];

Upvotes: 0

Bram Verstraten
Bram Verstraten

Reputation: 1557

Return your result as an array:

return [$return, $try];

Upvotes: 2

Related Questions