Closures in PHP

Closures in PHP

Have you ever wanted to generate functions on the fly with PHP like you can in JavaScript? Well, the short answer is that you can and it has everything to do with closures. I'll break it down briefly for you and then take you through an example, step by step, so you can see just how awesome "use" is in a function definition.

Closures Let You Define a Function that Defines a Function Using use

That heading may sound a bit confusing but it's the truth. You can write code that writes code. JavaScript programmers have to do this all the time in order to keep your browser from locking up constantly. They are always running into issues where the variables inside of one function need to be incorporated into the definition of another function before it will be of any use when it is called. Think of it this way: three people (functions) work in an office on three separate shifts. The girl on first shift has a message to pass to the guy on third shift. Since the two of them have no overlap in their schedule, they have to utilize the midget on second shift who, though tiny, has an enormous workload and important things to do. You could imagine the girl on first shift as having the function of sending a message and the guy on third shift having the function of receiving the message. Now the problem is obvious, the function on first shift will return before the function on third shift is even defined. Enter the midget. This four foot nine inch enabler gets it done, a real flexible worker with strong ethics and such. He serves the function of relaying the message because he's there in the morning to be defined by the return value of the first function and stays late so the third function can call him.

Now I know what you're thinking, "why not use email, a post it note, a cell phone for gosh sakes!". Well, if you have been listening to the internet (it never lies) you know that global variables are inherently evil. Anyone could come by during the day garbage collecting and toss out the note, the email servers could get hacked up, and who has time to sit around talking on the phone? We got a job to do here people! Besides, I haven't told you that they hired the poor midget without a well defined job description. If you asked him echo midget->job->description he would return null. Poor guy. So anyway, the message gets through and all is well. Just about the time our sweet midget is thinking about how to be more efficient at passing messages, that twit on first shift hands him a shovel and tells him to: push a pile of crap into the third shifters office, stand on his desk and wait for the guy to show up, then moon him. Now this is what he signed up for! Midge nearly does a cartwheel in excitement thinking about how very similar but oh so differently he will have to process this message! One day he's a simple relay and the next day he's a devious tool. You've got to watch out for midge. He's a real worker, he gets it done. . . and he's dangerous when used improperly.

Code Shows What Words Don't

But you are welcome to give Natural Language Programming a try... or not. Anyway, on to the long explanation that says more with code than all that gibberish above.

<?php

/*
* -- Kastor -- matthewkastor@live.com --
*
* See http://php.net/manual/en/functions.anonymous.php for
* good instructions.
*
* I liked the example I found on this page, it was easy to
* pick apart and use.
*
* http://stackoverflow.com/questions/1065188/in-php-5-3-0-
* what-is-the-function-use-identifier-
* should-a-sane-programmer-use
*
*/


/*
* Closures allow you to store anonymous functions within
* variables, and make really complex code easy to write.
* Programmers use closures all the time, because the
* bound variables aren't explicitly defined; that's what
* "use" is for.
*
* Note:
*
* PARAMETERS set values when the function is CALLED.
* CLOSURES set values when the function is DEFINED.
*
*/


/*
* Here is an example of a closure, it will give you an idea
* of how "use" is used.
*
* For instance: you have to sort an multidimensional array
* (TARGET 1) by a sub-value, but the key (DEFINE x) changes:
*
*/

function generateComparisonFunctionForKey($key) { // PARENT FUNCTION

return

// RETURNED FUNCTION (closure)
function ($left, $right) use ($key) {
if ($left[$key] == $right[$key])
return 0;
else
return ($left[$key] < $right[$key]) ? -1 : 1;
} // END OF RETURNED FUNCTION (closure)

; // note the semicolon, it's important for the return assignment

} // END OF PARENT FUNCTION


/*
* The parent function is given the value $key and returns an
* anonymous function which will take the parameters $left and
* $right, and act on them using the value given for $key.
*
* Basically, the parent function defines an anonymous
* function and returns it, i.e.
*
*/

// TARGET 1
$myArray = array(
array('name' => 'Kastor', 'age' => 30),
array('name' => 'Alex', 'age' => 70),
array('name' => 'Enrico', 'age' => 25)
);

$originalOrder = $myArray;

// DEFINE x
$sortByName = generateComparisonFunctionForKey('name');
$sortByAge = generateComparisonFunctionForKey('age');

/*
* When given the value 'name' for $key:
*
* generateComparisonFunctionForKey('name');
*
* Returns the closure as:
*
* function ($left, $right) {
* if ($left['name'] == $right['name'])
* return 0;
* else
* return ($left['name'] < $right['name']) ? -1 : 1;
* }
*
* Which is now an anonymous function.
*
*
* At this point, $sortByName and $sortByAge contain two
* different anonymous functions.
*
* $sortByName = function ($left, $right) {
* if ($left['name'] == $right['name'])
* return 0;
* else
* return ($left['name'] < $right['name']) ? -1 : 1;
* }
*
* AND
*
* $sortByAge = function ($left, $right) {
* if ($left['age'] == $right['age'])
* return 0;
* else
* return ($left['age'] < $right['age']) ? -1 : 1;
* }
*
*/

usort($myArray, $sortByName);
$nameOrder = $myArray;

usort($myArray, $sortByAge);
$ageOrder = $myArray;

/*
* The anonymous functions can now be used. In this case,
* they're comparison functions passed to usort.
*
* usort(
* $myArray = array(
* array('name' => 'Kastor', 'age' => 30),
* array('name' => 'Alex', 'age' => 70),
* array('name' => 'Enrico', 'age' => 25)
* ),
* $sortByName = function ($left, $right) {
* if ($left['name'] == $right['name'])
* return 0;
* else
* return ($left['name'] < $right['name']) ? -1 : 1;
* }
* );
*
* AND
*
* usort(
* $myArray = array(
* array('name' => 'Kastor', 'age' => 30),
* array('name' => 'Alex', 'age' => 70),
* array('name' => 'Enrico', 'age' => 25)
* ),
* $sortByAge = function ($left, $right) {
* if ($left['age'] == $right['age'])
* return 0;
* else
* return ($left['age'] < $right['age']) ? -1 : 1;
* }
* );
*
*
* The expected results are that the array stored in
* $myArray will have been sorted by age and by name, and with
* each sort the array key is replaced, hence the 3 variables
* for capturing the state of $myArray.
*
* Let's check.
*
*/

echo "\r\n-----------------\r\n".
'Original order:'."\r\n\r\n";

print_r($originalOrder);

echo "\r\n-----------------\r\n".
'Ordered by name:'."\r\n\r\n";

print_r($nameOrder);

echo "\r\n-----------------\r\n".
'Ordered by age:'."\r\n\r\n";

print_r($ageOrder);

/*
* Expected Output
*
* -----------------
* Original order:
*
* Array
* (
* [0] => Array
* (
* [name] => Kastor
* [age] => 30
* )
*
* [1] => Array
* (
* [name] => Alex
* [age] => 70
* )
*
* [2] => Array
* (
* [name] => Enrico
* [age] => 25
* )
*
* )
*
* -----------------
* Ordered by name:
*
* Array
* (
* [0] => Array
* (
* [name] => Alex
* [age] => 70
* )
*
* [1] => Array
* (
* [name] => Enrico
* [age] => 25
* )
*
* [2] => Array
* (
* [name] => Kastor
* [age] => 30
* )
*
* )
*
* -----------------
* Ordered by age:
*
* Array
* (
* [0] => Array
* (
* [name] => Enrico
* [age] => 25
* )
*
* [1] => Array
* (
* [name] => Kastor
* [age] => 30
* )
*
* [2] => Array
* (
* [name] => Alex
* [age] => 70
* )
*
* )
*
*/

?>