Chapter 6: Functions
Functions package work behind a name so you can reuse logic, return results, and keep code small and clear. PHP supports traditional functions, closures, and concise arrow functions. You can add types, defaults, attributes, and more for precise behavior.
Defining functions and return values
Define a function with the function keyword, then write parameters in parentheses and the body in braces. Return a value with return; omit it when you only need side effects.
Writing a basic function with a return value
Declare the function once, then call it wherever you need the result.
<?php
function areaOfCircle(float $r): float {
$pi = 3.14159;
return $pi * $r * $r;
}
echo areaOfCircle(2.5);
?>
Adding parameter and return types for clarity
Use type declarations on parameters and a return type after the signature. Union types allow several accepted types.
<?php
function toIntOrNull(int|string $v): ?int {
if (is_int($v)) {
return $v;
}
if (ctype_digit($v)) {
return (int)$v;
}
return null;
}
?>
declare(strict_types=1); at the top of files where you want strict parameter type checks; strict typing improves predictability.
Returning early to keep branching simple
Return as soon as you have the answer; this keeps nesting shallow and intent obvious.
<?php
function firstPositive(array $nums): ?int {
foreach ($nums as $n) {
if ($n > 0) {
return $n;
}
}
return null;
}
?>
Parameters, defaults, and named arguments
Parameters accept input. Defaults provide fallback values. Named arguments improve readability by pairing names with values at call sites.
Providing sensible defaults in the signature
Place defaulted parameters after required ones. A default must match the declared type or be null when the type allows it.
<?php
function greet(string $name, string $salutation = "Hello"): string {
return "$salutation, $name";
}
echo greet("Kai"); // Hello, Kai
echo greet("Kai", "Welcome"); // Welcome, Kai
?>
Calling with named arguments to improve clarity
Use name: value pairs at the call site. Order is flexible when you supply names.
<?php
function rectangle(int $width, int $height, bool $border = false): array {
return compact("width", "height", "border");
}
$config = rectangle(height: 200, width: 300, border: true);
?>
Accepting null with nullable types
Prefix the type with ? to allow null. Use a default of null when absence is meaningful.
<?php
function findUser(?int $id = null): array {
if ($id === null) {
return ["mode" => "guest"];
}
return ["id" => $id, "mode" => "member"];
}
?>
Variadics and unpacking
Variadics collect many values into one parameter. Unpacking spreads an array or Traversable into individual arguments where a list is required.
Gathering many inputs with a variadic ... parameter
Place ... before the parameter name to capture remaining arguments as an array.
<?php
function sumAll(int ...$nums): int {
$total = 0;
foreach ($nums as $n) {
$total += $n;
}
return $total;
}
echo sumAll(2, 4, 6);
?>
Unpacking arrays into parameters with ...
Use ... at the call site to unpack an array into separate arguments.
<?php
function joinWith(string $sep, string ...$parts): string {
return implode($sep, $parts);
}
$words = ["a", "b", "c"];
echo joinWith("-", ...$words);
?>
Unpacking arrays by key for named-like behavior
When a function expects an associative array, the spread operator can merge arrays to build the final input.
<?php
$base = ["host" => "localhost"];
$overrides = ["port" => 5432];
$config = [...$base, ...$overrides, "user" => "app"];
?>
Closures and arrow functions
Anonymous functions capture surrounding variables and can be passed around like values. Arrow functions offer compact syntax for single expressions.
Creating a Closure and capturing with use
Use use to import variables from the surrounding scope by value; add & to import by reference.
<?php
$factor = 10;
$scale = function (int $n) use ($factor): int {
return $n * $factor;
};
echo $scale(3); // 30
?>
Writing concise fn arrow functions
Arrow functions automatically capture variables by value and evaluate a single expression. The expression result becomes the return value.
<?php
$nums = [1, 2, 3, 4];
$doubled = array_map(fn($n) => $n * 2, $nums);
?>
function closure.
Returning closures from functions for customization
Generate specialized behavior by returning a closure configured with parameters.
<?php
function makePrefixer(string $prefix): callable {
return fn(string $s): string => $prefix . $s;
}
$warn = makePrefixer("[WARN] ");
echo $warn("Low memory");
?>
Scope, static variables, and recursion
Scope controls where names are visible. Static variables persist their value across calls. Recursion lets a function call itself to solve problems in smaller steps.
Understanding local scope and the global symbol table
Variables inside a function are local. To access a global, prefer passing it in; if you must, read it via the global keyword or $GLOBALS array.
<?php
$rate = 0.2;
function vat(float $net): float {
global $rate; // prefer dependency injection instead
return $net * (1 + $rate);
}
?>
Persisting data between calls with static
A static local variable keeps its value across calls. Use this for simple memoization or counters.
<?php
function nextId(): int {
static $id = 0;
$id++;
return $id;
}
echo nextId(); // 1
echo nextId(); // 2
?>
Applying recursion with a safe base case
Every recursive function needs a base case to stop calling itself. Verify argument sizes and use iteration when recursion depth might be large.
<?php
function factorial(int $n): int {
if ($n <= 1) {
return 1;
}
return $n * factorial($n - 1);
}
?>
Attributes on functions
Attributes attach structured metadata to declarations. Frameworks and tools read attributes to modify behavior at runtime or during reflection.
Declaring a #[Attribute] above a function
Place attributes in square brackets with a leading hash. Supply arguments like regular constructor calls.
<?php
#[Deprecated("Call newProcess() instead")]
function oldProcess(): void {
// …
}
?>
Reading attributes with reflection
Use the Reflection API to inspect attributes and their arguments at runtime.
<?php
$func = new ReflectionFunction('oldProcess');
foreach ($func->getAttributes() as $attr) {
$name = $attr->getName();
$args = $attr->getArguments();
echo "$name: " . json_encode($args) . PHP_EOL;
}
?>
Defining a custom attribute class for precise semantics
Mark your own attribute class with #[Attribute] so PHP recognizes it as an attribute target.
<?php
#[Attribute(Attribute::TARGET_FUNCTION)]
class Route {
public function __construct(
public string $method,
public string $path
) {}
}
#[Route(method: "GET", path: "/status")]
function status(): array {
return ["ok" => true];
}
?>