03: Functions & Closures - From JS to PHP
Functions & Closures: From JS to PHP
Section titled “Functions & Closures: From JS to PHP”Overview
Section titled “Overview”Functions in PHP and TypeScript/JavaScript share many concepts, but PHP’s approach to closures and variable scope has important differences. This chapter bridges your JavaScript knowledge to PHP’s function system.
Learning Objectives
Section titled “Learning Objectives”By the end of this chapter, you’ll be able to:
- ✅ Write functions with proper type hints in PHP
- ✅ Understand PHP’s closure scope and variable capture
- ✅ Create higher-order functions
- ✅ Use first-class functions and callbacks
- ✅ Work with callable types
- ✅ Implement generator functions (yield)
- ✅ Apply functional programming patterns
Code Examples
Section titled “Code Examples”📁 View Code Examples on GitHub
This chapter includes comprehensive function and closure examples:
- Closure variable capture patterns
- Higher-order functions
- Generators and lazy evaluation
- Currying and memoization techniques
Run the examples:
cd code/php-typescript-developers/chapter-03php closures.phpphp generators.phpFunction Declarations
Section titled “Function Declarations”TypeScript
Section titled “TypeScript”// Function declarationfunction add(a: number, b: number): number { return a + b;}
// Arrow functionconst multiply = (a: number, b: number): number => a * b;
// Optional parametersfunction greet(name: string, greeting?: string): string { return `${greeting ?? "Hello"}, ${name}!`;}
// Default parametersfunction createUser(name: string, age: number = 18): User { return { name, age };}
// Rest parametersfunction sum(...numbers: number[]): number { return numbers.reduce((a, b) => a + b, 0);}<?phpdeclare(strict_types=1);
// Function declarationfunction add(int $a, int $b): int { return $a + $b;}
// Arrow function (single expression only)$multiply = fn(int $a, int $b): int => $a * $b;
// Optional parameters (use nullable type + null default)function greet(string $name, ?string $greeting = null): string { return ($greeting ?? "Hello") . ", {$name}!";}
// Default parametersfunction createUser(string $name, int $age = 18): array { return compact('name', 'age');}
// Variadic parameters (rest parameters)function sum(int ...$numbers): int { return array_sum($numbers);}Key Differences:
- PHP uses
...before the parameter name (...$numbers) - PHP arrow functions use
fnkeyword - Optional parameters typically use nullable types with
nulldefaults
Closures and Variable Scope
Section titled “Closures and Variable Scope”The Critical Difference: Lexical Scope
Section titled “The Critical Difference: Lexical Scope”TypeScript/JavaScript:
// Variables are automatically capturedconst multiplier = 2;
const double = (x: number): number => { return x * multiplier; // ✅ Automatically accesses outer scope};
console.log(double(5)); // 10PHP:
<?php$multiplier = 2;
// ❌ Does NOT automatically capture outer variables$double = function(int $x): int { return $x * $multiplier; // ❌ Error: Undefined variable $multiplier};
// ✅ Must explicitly use 'use' clause$double = function(int $x) use ($multiplier): int { return $x * $multiplier; // ✅ Works};
echo $double(5); // 10PHP Arrow Functions (PHP 7.4+):
<?php$multiplier = 2;
// ✅ Arrow functions automatically capture variables$double = fn(int $x): int => $x * $multiplier;
echo $double(5); // 10Closure Variable Capture
Section titled “Closure Variable Capture”TypeScript:
let counter = 0;
const increment = (): void => { counter++; // ✅ Mutates outer variable};
increment();console.log(counter); // 1PHP (Reference Capture):
<?php$counter = 0;
// By value (default) - does NOT mutate outer variable$increment = function() use ($counter): void { $counter++; // ❌ Only modifies local copy};
$increment();echo $counter; // 0 (unchanged)
// By reference - DOES mutate outer variable$increment = function() use (&$counter): void { $counter++; // ✅ Modifies outer variable};
$increment();echo $counter; // 1 (changed)PHP Arrow Functions:
<?php$counter = 0;
// Arrow functions capture by value (cannot modify outer variables)$increment = fn() => $counter++; // ❌ Does not modify outer $counter
// For mutation, use traditional closure with reference$increment = function() use (&$counter): void { $counter++;};Higher-Order Functions
Section titled “Higher-Order Functions”Functions Returning Functions
Section titled “Functions Returning Functions”TypeScript:
const makeMultiplier = (factor: number) => { return (value: number): number => value * factor;};
const double = makeMultiplier(2);const triple = makeMultiplier(3);
console.log(double(5)); // 10console.log(triple(5)); // 15PHP:
<?phpdeclare(strict_types=1);
function makeMultiplier(int $factor): callable { return fn(int $value): int => $value * $factor;}
$double = makeMultiplier(2);$triple = makeMultiplier(3);
echo $double(5) . PHP_EOL; // 10echo $triple(5) . PHP_EOL; // 15Functions as Arguments (Callbacks)
Section titled “Functions as Arguments (Callbacks)”TypeScript:
const numbers = [1, 2, 3, 4, 5];
// map, filter, reduceconst doubled = numbers.map(x => x * 2);const evens = numbers.filter(x => x % 2 === 0);const sum = numbers.reduce((acc, val) => acc + val, 0);PHP:
<?php$numbers = [1, 2, 3, 4, 5];
// array_map, array_filter, array_reduce$doubled = array_map(fn($x) => $x * 2, $numbers);$evens = array_filter($numbers, fn($x) => $x % 2 === 0);$sum = array_reduce($numbers, fn($acc, $val) => $acc + $val, 0);Custom Higher-Order Function:
TypeScript:
function applyOperation<T>( items: T[], operation: (item: T) => T): T[] { return items.map(operation);}
const numbers = [1, 2, 3];const doubled = applyOperation(numbers, x => x * 2);PHP:
<?phpdeclare(strict_types=1);
/** * @template T * @param array<T> $items * @param callable(T): T $operation * @return array<T> */function applyOperation(array $items, callable $operation): array { return array_map($operation, $items);}
$numbers = [1, 2, 3];$doubled = applyOperation($numbers, fn($x) => $x * 2);Callable Type Hints
Section titled “Callable Type Hints”TypeScript Function Types
Section titled “TypeScript Function Types”type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;const multiply: MathOperation = (a, b) => a * b;
function calculate( a: number, b: number, operation: MathOperation): number { return operation(a, b);}PHP Callable Types
Section titled “PHP Callable Types”<?phpdeclare(strict_types=1);
// Simple callable type hintfunction calculate(int $a, int $b, callable $operation): int { return $operation($a, $b);}
// Using PHPStan/Psalm docblock for precise typing/** * @param callable(int, int): int $operation */function calculateWithDoc(int $a, int $b, callable $operation): int { return $operation($a, $b);}
// Usage$add = fn($a, $b) => $a + $b;$multiply = fn($a, $b) => $a * $b;
echo calculate(5, 3, $add); // 8echo calculate(5, 3, $multiply); // 15PHP Callable Formats:
<?php// 1. Anonymous function$callback = function() { return "hello"; };
// 2. Arrow function$callback = fn() => "hello";
// 3. Function name as string$callback = 'strlen';
// 4. Static method (array format)$callback = [MyClass::class, 'staticMethod'];
// 5. Object method (array format)$callback = [$object, 'method'];
// 6. First-class callable (PHP 8.1+)$callback = strlen(...);First-Class Callable Syntax (PHP 8.1+)
Section titled “First-Class Callable Syntax (PHP 8.1+)”TypeScript
Section titled “TypeScript”// Functions are first-class citizensconst fn = Math.max;console.log(fn(1, 2, 3)); // 3PHP (8.1+)
Section titled “PHP (8.1+)”<?php// Old way (string or array)$fn = 'max';echo $fn(1, 2, 3); // 3
// First-class callable syntax (8.1+)$fn = max(...);echo $fn(1, 2, 3); // 3
// Object methods$fn = $object->method(...);$result = $fn($arg);
// Static methods$fn = MyClass::staticMethod(...);$result = $fn($arg);Generators (yield)
Section titled “Generators (yield)”TypeScript Generators
Section titled “TypeScript Generators”function* generateNumbers(max: number): Generator<number> { for (let i = 0; i < max; i++) { yield i; }}
for (const num of generateNumbers(5)) { console.log(num); // 0, 1, 2, 3, 4}PHP Generators
Section titled “PHP Generators”<?phpfunction generateNumbers(int $max): Generator { for ($i = 0; $i < $max; $i++) { yield $i; }}
foreach (generateNumbers(5) as $num) { echo $num . PHP_EOL; // 0, 1, 2, 3, 4}Generator with Keys:
<?phpfunction generateKeyValue(): Generator { yield 'a' => 1; yield 'b' => 2; yield 'c' => 3;}
foreach (generateKeyValue() as $key => $value) { echo "{$key}: {$value}" . PHP_EOL;}Practical Example - Lazy Loading:
TypeScript:
function* readLargeFile(filename: string): Generator<string> { const lines = fs.readFileSync(filename, 'utf-8').split('\n'); for (const line of lines) { yield line; }}PHP:
<?phpfunction readLargeFile(string $filename): Generator { $handle = fopen($filename, 'r'); while (($line = fgets($handle)) !== false) { yield trim($line); } fclose($handle);}
// Memory-efficient: processes one line at a timeforeach (readLargeFile('large.txt') as $line) { // Process line}Recursion
Section titled “Recursion”TypeScript
Section titled “TypeScript”const factorial = (n: number): number => { if (n <= 1) return 1; return n * factorial(n - 1);};
console.log(factorial(5)); // 120<?phpdeclare(strict_types=1);
function factorial(int $n): int { if ($n <= 1) return 1; return $n * factorial($n - 1);}
echo factorial(5); // 120Tail-Call Optimization (Not in PHP):
PHP does not optimize tail calls, so deep recursion can cause stack overflow. Use iteration or trampolining for deep recursion.
Currying and Partial Application
Section titled “Currying and Partial Application”TypeScript Currying
Section titled “TypeScript Currying”// Curried functionconst add = (a: number) => (b: number) => a + b;
const add5 = add(5);console.log(add5(10)); // 15
// Generic curry helperfunction curry<A, B, C>(fn: (a: A, b: B) => C) { return (a: A) => (b: B) => fn(a, b);}
const multiply = (a: number, b: number) => a * b;const curriedMultiply = curry(multiply);const double = curriedMultiply(2);console.log(double(10)); // 20PHP Currying
Section titled “PHP Currying”<?phpdeclare(strict_types=1);
// Curried function$add = fn(int $a): callable => fn(int $b): int => $a + $b;
$add5 = $add(5);echo $add5(10); // 15
// Generic curry helperfunction curry(callable $fn): callable { return function(...$args) use ($fn): callable { return function(...$moreArgs) use ($fn, $args): mixed { return $fn(...array_merge($args, $moreArgs)); }; };}
$multiply = fn(int $a, int $b): int => $a * $b;$curriedMultiply = curry($multiply);$double = $curriedMultiply(2);echo $double(10); // 20Partial Application
Section titled “Partial Application”TypeScript:
const greet = (greeting: string, name: string) => `${greeting}, ${name}!`;
const sayHello = (name: string) => greet("Hello", name);const sayHi = (name: string) => greet("Hi", name);
console.log(sayHello("Alice")); // "Hello, Alice!"console.log(sayHi("Bob")); // "Hi, Bob!"PHP:
<?phpdeclare(strict_types=1);
$greet = fn(string $greeting, string $name): string => "{$greeting}, {$name}!";
// Partial application with arrow functions$sayHello = fn(string $name): string => $greet("Hello", $name);$sayHi = fn(string $name): string => $greet("Hi", $name);
echo $sayHello("Alice"); // "Hello, Alice!"echo $sayHi("Bob"); // "Hi, Bob!"
// Or with traditional closures for clarity$sayHello = function(string $name) use ($greet): string { return $greet("Hello", $name);};Memoization Pattern
Section titled “Memoization Pattern”TypeScript Memoization
Section titled “TypeScript Memoization”function memoize<T extends (...args: any[]) => any>(fn: T): T { const cache = new Map();
return ((...args: any[]) => { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn(...args); cache.set(key, result); return result; }) as T;}
const expensiveOperation = (n: number): number => { console.log(`Computing ${n}...`); return n * n;};
const memoized = memoize(expensiveOperation);console.log(memoized(5)); // "Computing 5..." -> 25console.log(memoized(5)); // 25 (cached, no log)PHP Memoization
Section titled “PHP Memoization”<?phpdeclare(strict_types=1);
function memoize(callable $fn): callable { $cache = [];
return function(...$args) use ($fn, &$cache): mixed { $key = json_encode($args);
if (!isset($cache[$key])) { $cache[$key] = $fn(...$args); }
return $cache[$key]; };}
$expensiveOperation = function(int $n): int { echo "Computing {$n}...\n"; return $n * $n;};
$memoized = memoize($expensiveOperation);echo $memoized(5) . PHP_EOL; // "Computing 5..." -> 25echo $memoized(5) . PHP_EOL; // 25 (cached, no log)echo $memoized(10) . PHP_EOL; // "Computing 10..." -> 100Key Point: PHP requires &$cache to modify the cache array across calls.
Common Closure Pitfalls
Section titled “Common Closure Pitfalls”Pitfall 1: Forgetting to Capture Variables
Section titled “Pitfall 1: Forgetting to Capture Variables”<?php$multiplier = 10;
// ❌ BAD: Closure doesn't capture $multiplier$double = function(int $x): int { return $x * $multiplier; // Error: Undefined variable};
// ✅ GOOD: Explicit capture$double = function(int $x) use ($multiplier): int { return $x * $multiplier;};
// ✅ BETTER: Use arrow function for auto-capture$double = fn(int $x): int => $x * $multiplier;Pitfall 2: Capturing by Value vs Reference
Section titled “Pitfall 2: Capturing by Value vs Reference”<?php$counter = 0;
// ❌ WRONG: Captures by value (doesn't mutate outer)$increment = function() use ($counter): void { $counter++; // Only modifies local copy};
$increment();echo $counter; // Still 0
// ✅ CORRECT: Capture by reference$counter = 0;$increment = function() use (&$counter): void { $counter++; // Modifies outer variable};
$increment();echo $counter; // 1Pitfall 3: Late Binding in Loops
Section titled “Pitfall 3: Late Binding in Loops”TypeScript:
const callbacks: (() => void)[] = [];
for (let i = 0; i < 3; i++) { callbacks.push(() => console.log(i)); // ✅ Each closure captures its own i}
callbacks.forEach(cb => cb()); // 0, 1, 2PHP (Problematic):
<?php$callbacks = [];
// ❌ WRONG: All closures share same $i referencefor ($i = 0; $i < 3; $i++) { $callbacks[] = function() use ($i) { echo $i . PHP_EOL; };}
foreach ($callbacks as $cb) { $cb(); // 2, 2, 2 (all capture final value)}
// ✅ CORRECT: Capture by value explicitly$callbacks = [];for ($i = 0; $i < 3; $i++) { $callbacks[] = function() use ($i) { // Captures current value echo $i . PHP_EOL; };}
foreach ($callbacks as $cb) { $cb(); // 0, 1, 2}
// ✅ ALTERNATIVE: Use arrow function$callbacks = [];for ($i = 0; $i < 3; $i++) { $callbacks[] = fn() => print($i . PHP_EOL);}Explanation: In the first example, if you use use (&$i) (reference), all closures point to the same variable, which ends at 2. Using use ($i) (value) captures the value at creation time.
Pitfall 4: Arrow Functions Can’t Modify Captured Variables
Section titled “Pitfall 4: Arrow Functions Can’t Modify Captured Variables”<?php$count = 0;
// ❌ Arrow functions can't modify captured variables$increment = fn() => $count++; // Modifies local copy, not outer
$increment();echo $count; // Still 0
// ✅ Use traditional closure with reference$increment = function() use (&$count): void { $count++;};
$increment();echo $count; // 1Pitfall 5: Closure Serialization
Section titled “Pitfall 5: Closure Serialization”<?php// ❌ Closures cannot be serialized$fn = fn($x) => $x * 2;$serialized = serialize($fn); // Error: Serialization of 'Closure' is not allowed
// ✅ Use named functions or Opis/Closure libraryfunction double(int $x): int { return $x * 2;}
$serialized = serialize('double'); // OK$fn = unserialize($serialized);echo $fn(5); // 10Function Composition
Section titled “Function Composition”TypeScript Compose
Section titled “TypeScript Compose”const compose = <T>(...fns: Array<(arg: T) => T>) => { return (value: T): T => { return fns.reduceRight((acc, fn) => fn(acc), value); };};
const add2 = (x: number) => x + 2;const multiply3 = (x: number) => x * 3;const square = (x: number) => x ** 2;
const compute = compose(square, multiply3, add2);console.log(compute(5)); // square(multiply3(add2(5))) = square(21) = 441PHP Compose
Section titled “PHP Compose”<?phpdeclare(strict_types=1);
function compose(callable ...$functions): callable { return function(mixed $value) use ($functions): mixed { return array_reduce( array_reverse($functions), fn($carry, $fn) => $fn($carry), $value ); };}
$add2 = fn(int $x): int => $x + 2;$multiply3 = fn(int $x): int => $x * 3;$square = fn(int $x): int => $x ** 2;
$compute = compose($square, $multiply3, $add2);echo $compute(5); // 441Closure Binding and $this Context
Section titled “Closure Binding and $this Context”TypeScript this Binding
Section titled “TypeScript this Binding”class Counter { private count = 0;
// Arrow function preserves this increment = () => { this.count++; };
// Regular method needs binding incrementMethod() { this.count++; }
getCount() { return this.count; }}
const counter = new Counter();const inc = counter.increment;inc(); // ✅ Works (arrow function bound to instance)
const incMethod = counter.incrementMethod;incMethod(); // ❌ Error: this is undefinedPHP Closure Binding
Section titled “PHP Closure Binding”<?phpdeclare(strict_types=1);
class Counter { private int $count = 0;
public function increment(): void { $this->count++; }
public function getCount(): int { return $this->count; }
public function getIncrementer(): callable { // Closure has access to $this automatically return function(): void { $this->count++; // ✅ Works }; }
public function getArrowIncrementer(): callable { // Arrow function also has $this access return fn() => $this->count++; }}
$counter = new Counter();$inc = $counter->getIncrementer();$inc();echo $counter->getCount(); // 1
// Rebinding closure to different object$counter2 = new Counter();$boundInc = $inc->bindTo($counter2);$boundInc();echo $counter2->getCount(); // 1Key Difference: PHP closures defined inside methods automatically have access to $this, unlike JavaScript where you need arrow functions or explicit binding.
Practical Example: Event Emitter
Section titled “Practical Example: Event Emitter”Let’s build a simple event emitter in both languages:
TypeScript
Section titled “TypeScript”class EventEmitter { private listeners: Map<string, Array<(...args: any[]) => void>> = new Map();
on(event: string, callback: (...args: any[]) => void): void { if (!this.listeners.has(event)) { this.listeners.set(event, []); } this.listeners.get(event)!.push(callback); }
emit(event: string, ...args: any[]): void { const callbacks = this.listeners.get(event); if (callbacks) { callbacks.forEach(callback => callback(...args)); } }}
// Usageconst emitter = new EventEmitter();emitter.on('message', (msg: string) => console.log(`Received: ${msg}`));emitter.emit('message', 'Hello!'); // "Received: Hello!"<?phpdeclare(strict_types=1);
class EventEmitter { /** @var array<string, array<callable>> */ private array $listeners = [];
public function on(string $event, callable $callback): void { if (!isset($this->listeners[$event])) { $this->listeners[$event] = []; } $this->listeners[$event][] = $callback; }
public function emit(string $event, mixed ...$args): void { if (isset($this->listeners[$event])) { foreach ($this->listeners[$event] as $callback) { $callback(...$args); } } }}
// Usage$emitter = new EventEmitter();$emitter->on('message', fn($msg) => echo "Received: {$msg}" . PHP_EOL);$emitter->emit('message', 'Hello!'); // "Received: Hello!"Hands-On Exercise
Section titled “Hands-On Exercise”Task 1: Implement Array Methods
Section titled “Task 1: Implement Array Methods”Implement myMap, myFilter, and myReduce functions in PHP:
Goal:
<?php$numbers = [1, 2, 3, 4, 5];$doubled = myMap($numbers, fn($x) => $x * 2);$evens = myFilter($numbers, fn($x) => $x % 2 === 0);$sum = myReduce($numbers, fn($acc, $val) => $acc + $val, 0);Solution
<?phpdeclare(strict_types=1);
/** * @template T * @template U * @param array<T> $array * @param callable(T): U $callback * @return array<U> */function myMap(array $array, callable $callback): array { $result = []; foreach ($array as $item) { $result[] = $callback($item); } return $result;}
/** * @template T * @param array<T> $array * @param callable(T): bool $callback * @return array<T> */function myFilter(array $array, callable $callback): array { $result = []; foreach ($array as $item) { if ($callback($item)) { $result[] = $item; } } return $result;}
/** * @template T * @template U * @param array<T> $array * @param callable(U, T): U $callback * @param U $initial * @return U */function myReduce(array $array, callable $callback, mixed $initial): mixed { $accumulator = $initial; foreach ($array as $item) { $accumulator = $callback($accumulator, $item); } return $accumulator;}
// Test$numbers = [1, 2, 3, 4, 5];$doubled = myMap($numbers, fn($x) => $x * 2);$evens = myFilter($numbers, fn($x) => $x % 2 === 0);$sum = myReduce($numbers, fn($acc, $val) => $acc + $val, 0);
print_r($doubled); // [2, 4, 6, 8, 10]print_r($evens); // [2, 4]echo $sum; // 15Task 2: Pipeline Function
Section titled “Task 2: Pipeline Function”Create a pipe function that chains multiple operations:
Goal:
<?php$result = pipe( 5, fn($x) => $x * 2, // 10 fn($x) => $x + 3, // 13 fn($x) => $x ** 2 // 169);echo $result; // 169Solution
<?phpdeclare(strict_types=1);
function pipe(mixed $value, callable ...$functions): mixed { foreach ($functions as $fn) { $value = $fn($value); } return $value;}
// Test$result = pipe( 5, fn($x) => $x * 2, // 10 fn($x) => $x + 3, // 13 fn($x) => $x ** 2 // 169);
echo $result; // 169Alternative with array_reduce:
<?phpfunction pipe(mixed $value, callable ...$functions): mixed { return array_reduce( $functions, fn($carry, $fn) => $fn($carry), $value );}Task 3: Debounce Function
Section titled “Task 3: Debounce Function”Implement a simple debounce function:
Solution
<?phpdeclare(strict_types=1);
function debounce(callable $callback, int $delayMs): callable { $lastCall = 0;
return function(...$args) use ($callback, $delayMs, &$lastCall) { $now = (int)(microtime(true) * 1000);
if ($now - $lastCall >= $delayMs) { $lastCall = $now; return $callback(...$args); }
return null; };}
// Usage$logMessage = debounce( fn($msg) => echo "Log: {$msg}" . PHP_EOL, 1000 // 1 second debounce);
$logMessage("Hello"); // Prints$logMessage("World"); // Ignored (within 1s)sleep(2);$logMessage("Again"); // Prints (after 1s)Key Takeaways
Section titled “Key Takeaways”- Closures require explicit variable capture with
useclause (except arrow functions) - Arrow functions (
fn) automatically capture variables but only support single expressions - Variable capture by reference requires
&inuse (&$var)for mutation - Arrow functions can’t modify captured variables—use traditional closures with
&for that - Callable type accepts functions, closures, method references, and string function names
- First-class callable syntax (
fn(...)) available in PHP 8.1+ for cleaner references - Generators work identically to TypeScript/JavaScript for lazy evaluation
- No tail-call optimization in PHP—use iteration for deep recursion
- Currying and partial application possible but require manual implementation
- Memoization requires
&$cachereference to persist cache across invocations - Loop variable capture behaves differently—use by-value
use ($i)to capture current iteration - Closures cannot be serialized—use named functions or external libraries
- Function composition can be implemented with
array_reduceandarray_reverse - PHP closures in methods automatically have access to
$this(no need for binding like JS) bindTo()method allows rebinding closures to different object instances
Comparison Table
Section titled “Comparison Table”| Feature | TypeScript | PHP |
|---|---|---|
| Arrow Function | (x) => x * 2 | fn($x) => $x * 2 |
| Multi-line Arrow | ✅ | ❌ (use function) |
| Auto Capture Vars | ✅ | ✅ (arrow), ❌ (function) |
| Explicit Capture | N/A | use ($var) |
| Reference Capture | Default | use (&$var) |
| Generators | function* | function + yield |
| Callable Type | (a: T) => U | callable |
| First-Class Callable | Native | fn(...) (PHP 8.1+) |
| Tail-Call Optimization | Engine-dependent | ❌ |
Next Steps
Section titled “Next Steps”Now that you understand functions and closures, let’s explore object-oriented programming in PHP.
Next Chapter: 04: OOP: Classes, Interfaces & Generics
Resources
Section titled “Resources”- PHP Closures Documentation
- PHP Arrow Functions RFC
- PHP Generators Documentation
- PHP First-Class Callable Syntax
Questions or feedback? Open an issue on GitHub