Declaring a Function
<?php
function greet() {
echo "Hello!\n";
}
greet(); // Hello!
// With parameters and return
function add($a, $b) {
return $a + $b;
}
echo add(3, 4); // 7
Parameters & Type Hints
Modern PHP supports rich type declarations on parameters:
<?php
declare(strict_types=1);
function divide(int $a, int $b): float {
return $a / $b;
}
// Nullable type
function findUser(?int $id): ?User { /* ... */ }
// Union type (PHP 8.0+)
function format(int|string $value): string {
return (string) $value;
}
// Intersection type (PHP 8.1+) - object must implement BOTH
function process(Iterator&Countable $data) { /* ... */ }
Default Values
<?php
function greet(string $name = "Guest", string $greeting = "Hello"): string {
return "$greeting, $name!";
}
echo greet(); // Hello, Guest!
echo greet("Ruban"); // Hello, Ruban!
echo greet("Ruban", "Welcome"); // Welcome, Ruban!
// Defaults must come after required params
function bad(string $a = "x", string $b) {} // ERROR
function ok(string $b, string $a = "x") {} // OK
Return Types
<?php
function isAdult(int $age): bool {
return $age >= 18;
}
// void - function returns nothing
function log(string $msg): void {
file_put_contents("app.log", $msg, FILE_APPEND);
}
// never - function never returns (throws or exits)
function abort(string $msg): never {
throw new RuntimeException($msg);
}
// static - returns instance of the same class (PHP 8.0+)
class Builder {
public function chain(): static { return $this; }
}
Variadic & Spread (...)
<?php
// Variadic - collect all extra args into an array
function sum(int ...$numbers): int {
return array_sum($numbers);
}
echo sum(1, 2, 3); // 6
echo sum(1, 2, 3, 4, 5, 6); // 21
// Spread - expand an array into individual args
$nums = [10, 20, 30];
echo sum(...$nums); // 60
// Combine fixed + variadic
function logEvent(string $level, string ...$messages): void {
foreach ($messages as $msg) {
echo "[$level] $msg\n";
}
}
Named Arguments (PHP 8.0+)
<?php
function createUser(
string $name,
string $email,
string $role = "user",
bool $active = true,
?string $department = null,
) { /* ... */ }
// Without named args - what does each value mean?
createUser("Ruban", "ruban@x.com", "admin", false, "Eng");
// With named args - intent crystal clear
createUser(
name: "Ruban",
email: "ruban@x.com",
role: "admin",
active: false,
department: "Eng",
);
// Skip middle defaults
createUser(name: "Ruban", email: "ruban@x.com", department: "Eng");
Arrow Functions (fn) (PHP 7.4+)
Single-expression closures with automatic value capture:
<?php
$nums = [1, 2, 3, 4, 5];
// Arrow function - auto-captures outer variables
$tax = 0.10;
$withTax = array_map(fn($n) => $n + $n * $tax, $nums);
// Equivalent classic closure
$withTax = array_map(function($n) use ($tax) {
return $n + $n * $tax;
}, $nums);
// Sort callback
usort($users, fn($a, $b) => $a->age <=> $b->age);
// Filter
$adults = array_filter($users, fn($u) => $u->age >= 18);
Mutations to outer variables inside an arrow function are not visible outside. If you need to mutate, use a regular closure with use (&$var).
Closures & use
<?php
// Closure stored in a variable
$multiply = function ($a, $b) {
return $a * $b;
};
echo $multiply(3, 4); // 12
// Capture outer variable by value
$prefix = "user_";
$prefixer = function ($name) use ($prefix) {
return $prefix . $name;
};
echo $prefixer("alice"); // user_alice
// Capture by reference - mutations visible to outer
$count = 0;
$increment = function () use (&$count) {
$count++;
};
$increment(); $increment(); $increment();
echo $count; // 3
// Capture multiple
$header = "App: ";
$debug = true;
$log = function ($msg) use ($header, $debug) {
if ($debug) echo $header . $msg;
};
First-Class Callable Syntax (PHP 8.1+)
<?php
// Old way - pass functions by name string
$upper = array_map("strtoupper", $words);
// PHP 8.1+ - first-class callable syntax
$upper = array_map(strtoupper(...), $words);
// Works with methods too
$callbacks = [
$user->save(...),
$logger->info(...),
User::create(...),
];
// IDE can verify these now - typo-safe and refactorable
Recursion
A function calling itself. Useful for tree structures - avoid for sequential work where loops are simpler.
<?php
// Factorial
function factorial(int $n): int {
return $n <= 1 ? 1 : $n * factorial($n - 1);
}
echo factorial(5); // 120
// Walk a directory tree
function listFiles(string $dir): array {
$files = [];
foreach (scandir($dir) as $entry) {
if ($entry === "." || $entry === "..") continue;
$path = "$dir/$entry";
if (is_dir($path)) {
$files = array_merge($files, listFiles($path));
} else {
$files[] = $path;
}
}
return $files;
}
PHP doesn't optimize tail calls. Default stack supports ~256 frames before Maximum function nesting level error (with Xdebug) or segfault. For deep recursion, convert to an iterative loop with an explicit stack.