Lab 05: Functions & Closures

Objective

Define and call functions with typed parameters, default values, variadic args, and return types. Write closures and arrow functions. Use first-class callable syntax and higher-order functions.

Background

PHP 8 has a mature function system with union types, named arguments, readonly properties, and first-class callable syntax. Closures are objects in PHP (Closure class) — they can be stored, passed, and bound to different objects. Understanding PHP's function capabilities is essential for modern PHP frameworks like Laravel and Symfony.

Time

35 minutes

Prerequisites

  • Lab 04 (Control Flow)

Tools

  • PHP 8.3 CLI

  • Docker image: zchencow/innozverse-php:latest


Lab Instructions

Step 1: Function Basics & Type Declarations

💡 declare(strict_types=1) at the top of a file enables strict type checking — passing a float to an int parameter throws a TypeError instead of silently converting. Always use it in modern PHP code to catch type errors early.

📸 Verified Output:


Step 2: Named Arguments & Variadic Functions

💡 Named arguments (PHP 8.0+) let you skip optional parameters and document intent. createUser(name: 'Alice', role: 'admin') is clearer than createUser('Alice', 0, 'admin', true). They also make function calls resilient to parameter reordering.

📸 Verified Output:


Step 3: Closures

💡 use captures by value by default. If the outer variable changes after the closure is defined, the closure still sees the original value. Use use (&$var) to capture by reference — then both the closure and outer scope share the same variable.

📸 Verified Output:


Step 4: Arrow Functions

💡 Arrow functions (fn) automatically capture outer variables by value without use — they read the enclosing scope implicitly. They're single-expression (no {}), always return the expression result, and can't modify outer variables (no & capture). Perfect for short callbacks.

📸 Verified Output:


Step 5: Higher-Order Functions

💡 compose(f, g, h)($x) = f(g(h($x))) — right to left like math. pipe($x, f, g, h) = h(g(f($x))) — left to right, more readable. Both are fundamental FP patterns. Memoization is safe only for pure functions (same input always gives same output, no side effects).

📸 Verified Output:


Step 6: Recursion

💡 PHP's default stack depth handles ~100–1000 recursive calls before stack overflow. For deep recursion, use ini_set('xdebug.max_nesting_level', 1000) or convert to iteration. array_merge inside recursion is O(n) each call — for large arrays, use a reference-based approach.

📸 Verified Output:


Step 7: Static Functions & Built-in Callables

💡 PHP built-in functions are valid callables — you can pass 'strtolower', 'trim', 'is_int' as strings to array_map, array_filter, usort. This works because PHP looks them up by name. The first-class callable syntax strtolower(...) creates a Closure — more type-safe.

📸 Verified Output:


Step 8: Complete Example — Pipeline Processor

💡 The Pipeline pattern chains transformations functionally. Each pipe() returns a clone (immutable) so you can branch pipelines. This is how Laravel's pipeline, middleware stacks, and Guzzle request handlers work internally. The static return type hint enables method chaining with subclasses.

📸 Verified Output:


Verification

Expected: Pipeline result: 400 (5 × 2 = 10, + 10 = 20, 20² = 400)

Summary

PHP functions are first-class citizens — typed, composable, and flexible. You've covered type declarations, named arguments, variadic functions, closures with use, arrow functions with implicit capture, higher-order functions, recursion, and a full Pipeline pattern. These skills underpin every modern PHP framework.

Further Reading

Last updated