PHP Functions

Functions are reusable, testable chunks of logic. Master typed parameters, return types, default values, variadic args, arrow functions, and closures.

Beginner 10 min read 14 examples

Declaring a Function

PHP
<?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
<?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
<?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
<?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
<?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
<?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
<?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);
Arrow functions capture by VALUE only

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
<?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
<?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
<?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;
}
Recursion has a stack limit

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.

Next Steps

Frequently Asked Questions

Use arrow functions (fn() => ...) for one-line expressions that need outer variables - they auto-capture by value with no use clause. Use closures when you need multiple statements or need to modify captured variables via use (&$var).

No. strlen(), STRLEN(), and Strlen() all work. Variables, however, ARE case-sensitive. Always use lowercase for function names by convention.

Yes - especially on public functions. Types document intent, enable IDE autocomplete, and catch bugs early. Combine with declare(strict_types=1); at the top of files for runtime enforcement.

They're equivalent. ?string is shorthand for string|null. Use whichever reads more naturally. For unions with other types (int|string|null) you must spell it out.

Not by default - PHP passes by value. Either return the new value, accept the variable by reference (function f(&$x)), or use an object (object handles are shared).