Closures and Higher-Order Functions
Anonymous functions
Assign a function to a variable without naming it:
$double = function($x) {
return $x * 2;
};
echo $double(5); // 10
Anonymous functions are commonly used as callbacks:
$numbers = [3, 1, 4, 1, 5, 9];
$evens = @Array.filter($numbers, function($n) { return $n % 2 == 0; });
// [4]
$doubled = @Array.map($numbers, function($n) { return $n * 2; });
// [6, 2, 8, 2, 10, 18]
The use clause
Functions in Zymba have isolated scope — they cannot access variables from the surrounding context. To capture outer variables in an anonymous function, use the use clause:
$taxRate = 0.08;
$addTax = function($price) use ($taxRate) {
return $price * (1 + $taxRate);
};
echo $addTax(100); // 108
Without use, the outer variable is not accessible:
$taxRate = 0.08;
$addTax = function($price) {
return $price * (1 + $taxRate); // $taxRate is undefined here!
};
Capturing multiple variables
Separate multiple captured variables with commas:
$baseUrl = "https://api.example.com";
$apiKey = "secret-key";
$makeRequest = function($endpoint) use ($baseUrl, $apiKey) {
$url = $baseUrl . $endpoint;
$headers = ["Authorization": "Bearer " . $apiKey];
return @HTTP.request($url, "GET", null, $headers);
};
Variables are captured by value
Variables captured with use are captured by value, not by reference. Modifying a captured variable inside the closure does not affect the original:
$count = 0;
$increment = function() use ($count) {
$count++; // Only modifies the local copy
return $count;
};
echo $increment(); // 1
echo $increment(); // 1 (not 2 — each call gets a fresh copy)
echo $count; // 0 (original unchanged)
Recursion with closures
Named functions cannot call themselves recursively because the function variable is not in scope inside its own body. For recursive closures, capture the function itself:
$factorial = function($n) use ($factorial) {
if ($n <= 1) {
return 1;
}
return $n * $factorial($n - 1);
};
echo $factorial(5); // 120
Higher-order functions
Functions as arguments
Pass functions to other functions:
function $apply($fn, $value) {
return $fn($value);
}
$double = function($x) { return $x * 2; };
echo $apply($double, 21); // 42
Functions as return values (factory pattern)
Return functions from other functions:
function $createMultiplier($factor) {
return function($x) use ($factor) {
return $x * $factor;
};
}
$triple = $createMultiplier(3);
$tenX = $createMultiplier(10);
echo $triple(5); // 15
echo $tenX(5); // 50
Validation builder
function $createValidator($minLength, $maxLength) {
return function($value) use ($minLength, $maxLength) {
if (count $value < $minLength) {
return "Too short (min $minLength)";
}
if (count $value > $maxLength) {
return "Too long (max $maxLength)";
}
return null; // Valid
};
}
$validateName = $createValidator(2, 50);
$validateCode = $createValidator(4, 10);
$error = $validateName("A"); // "Too short (min 2)"
$error = $validateCode("ABCDE"); // null (valid)
Patterns from production code
API client with closures
function $createApiClient($baseUrl, $apiKey) {
$request = function($method, $path, $data = null) use ($baseUrl, $apiKey) {
$url = $baseUrl . $path;
$headers = [
"Authorization": "Bearer " . $apiKey,
"Content-Type": "application/json"
];
$body = ($data is null) ? null : @Var.toJSON($data);
return @Var.fromJSON(@HTTP.request($url, $method, $body, $headers));
};
return [
get: function($path) use ($request) { return $request("GET", $path); },
post: function($path, $data) use ($request) { return $request("POST", $path, $data); }
];
}
Processing pipelines
$pipeline = [
function($v) { return @String.trim($v); },
function($v) { return @String.toLower($v); },
function($v) { return @Regex.replace($v, "/[^a-z0-9]/", "-"); }
];
$input = " Hello World! ";
$result = $input;
for ($pipeline as $fn) {
$result = $fn($result);
}
echo $result; // "hello-world-"