Intermediate

Functions in PHP

Learn how to define and use functions in PHP - parameters, return values, default and named arguments, scope, recursion, closures, and arrow functions with Docker-ready examples

Functions are the building blocks of reusable code. They let you package a piece of logic behind a name, call it as many times as you need, and pass data in and out cleanly. Instead of repeating the same lines throughout a script, you write the logic once and invoke it wherever required.

PHP is a multi-paradigm language, and its function support reflects that. You can write plain procedural functions in the C tradition, attach type hints for safety, take advantage of default and named arguments, and treat functions as first-class values for a functional style. Because PHP is dynamically and weakly typed, type declarations on parameters and return values are optional — but they make code far easier to reason about and are standard practice in modern PHP.

In this tutorial you’ll learn how to define functions, pass parameters and return values, use default and named arguments, understand variable scope, write recursive functions, and work with PHP’s first-class functions: closures and arrow functions.

Defining and Calling Functions

A function is declared with the function keyword, a name, a parameter list, and a body. Modern PHP lets you add type declarations for parameters and a return type after the parameter list.

Create a file named functions.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
declare(strict_types=1);

// A simple function with no parameters
function greet(): string
{
    return "Hello from a function!";
}

// A function with typed parameters and a typed return value
function add(int $a, int $b): int
{
    return $a + $b;
}

// A function that performs an action but returns nothing
function announce(string $name): void
{
    echo "Now presenting: {$name}\n";
}

echo greet() . "\n";
echo "3 + 4 = " . add(3, 4) . "\n";
announce("PHP Functions");

The declare(strict_types=1) line at the top enforces strict type checking, so passing a string where an int is expected raises an error instead of silently coercing the value. The void return type documents that a function returns no useful value.

Default and Named Arguments

PHP lets you give parameters default values, making them optional when calling the function. Since PHP 8.0, you can also pass arguments by name, which makes calls self-documenting and lets you skip optional parameters without listing every one before them.

Create a file named arguments.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
declare(strict_types=1);

// $greeting and $punctuation have default values
function makeMessage(string $name, string $greeting = "Hello", string $punctuation = "!"): string
{
    return "{$greeting}, {$name}{$punctuation}";
}

// Positional arguments
echo makeMessage("Ada") . "\n";
echo makeMessage("Grace", "Welcome") . "\n";

// Named arguments - skip $greeting, only set $punctuation
echo makeMessage("Linus", punctuation: "...") . "\n";

// Named arguments can be given in any order
echo makeMessage(punctuation: "?", name: "Margaret", greeting: "Is it you") . "\n";

When you call makeMessage("Ada"), the two defaults fill in automatically. Named arguments (punctuation: "...") let you target a specific optional parameter while leaving the others at their defaults.

Variable Scope

Variables defined inside a function are local to that function and don’t leak into the surrounding code. Likewise, functions can’t see variables from the outer scope unless you explicitly bring them in with the global keyword or pass them as arguments. PHP also supports static local variables that retain their value between calls.

Create a file named scope.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
declare(strict_types=1);

$message = "I live in the global scope";

function showScope(): void
{
    // This local $message is separate from the global one
    $message = "I live inside the function";
    echo $message . "\n";

    // To access the global variable, declare it
    global $message;
    echo $message . "\n";
}

// A static variable keeps its value across calls
function counter(): int
{
    static $count = 0;
    $count++;
    return $count;
}

showScope();
echo "Call 1: " . counter() . "\n";
echo "Call 2: " . counter() . "\n";
echo "Call 3: " . counter() . "\n";

The first echo inside showScope() prints the local value. After global $message, the function rebinds $message to the global variable, so the second echo prints the global text. The counter() function uses a static variable that survives between calls, incrementing each time.

Recursion

A recursive function calls itself to solve a problem by breaking it into smaller subproblems. Every recursive function needs a base case to stop the recursion. The classic example is computing a factorial.

Create a file named recursion.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
declare(strict_types=1);

// factorial(n) = n * factorial(n - 1), with factorial(0) = 1
function factorial(int $n): int
{
    if ($n <= 1) {
        return 1; // base case
    }
    return $n * factorial($n - 1); // recursive case
}

// The Fibonacci sequence: each number is the sum of the previous two
function fibonacci(int $n): int
{
    if ($n < 2) {
        return $n; // base cases: fib(0) = 0, fib(1) = 1
    }
    return fibonacci($n - 1) + fibonacci($n - 2);
}

echo "5! = " . factorial(5) . "\n";
echo "10! = " . factorial(10) . "\n";

echo "First 10 Fibonacci numbers: ";
for ($i = 0; $i < 10; $i++) {
    echo fibonacci($i) . " ";
}
echo "\n";

factorial(5) expands to 5 * 4 * 3 * 2 * 1 = 120, and factorial(10) is 3628800. The fibonacci() function calls itself twice per step, building the familiar sequence starting from 0 and 1.

Higher-Order Functions, Closures, and Arrow Functions

In PHP, functions are first-class values. You can store them in variables, pass them as arguments, and return them from other functions. An anonymous function (closure) is created with function () { ... } and can capture outer variables with use. PHP 7.4 introduced arrow functions (fn () => ...), which capture outer variables automatically and are ideal for short callbacks.

Create a file named closures.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
declare(strict_types=1);

// A closure stored in a variable
$square = function (int $x): int {
    return $x * $x;
};

echo "Square of 6: " . $square(6) . "\n";

// A closure capturing an outer variable with `use`
$factor = 3;
$multiply = function (int $x) use ($factor): int {
    return $x * $factor;
};
echo "4 times {$factor}: " . $multiply(4) . "\n";

// Arrow functions capture outer variables automatically
$numbers = [1, 2, 3, 4, 5];

// array_map applies a callback to every element
$squares = array_map(fn (int $n): int => $n * $n, $numbers);
echo "Squares: " . implode(", ", $squares) . "\n";

// array_filter keeps elements where the callback returns true
$evens = array_filter($numbers, fn (int $n): bool => $n % 2 === 0);
echo "Evens: " . implode(", ", $evens) . "\n";

// A function that returns a function (a "multiplier factory")
function makeMultiplier(int $factor): callable
{
    return fn (int $x): int => $x * $factor;
}

$double = makeMultiplier(2);
$triple = makeMultiplier(3);
echo "Double 7: " . $double(7) . "\n";
echo "Triple 7: " . $triple(7) . "\n";

array_map and array_filter are higher-order functions: they take another function as an argument. The arrow function fn (int $n): int => $n * $n is shorthand that automatically captures $numbers from the surrounding scope. makeMultiplier() returns a closure that remembers its $factor, demonstrating how functions can produce other functions.

Running with Docker

1
2
3
4
5
6
7
8
9
# Pull the official image
docker pull php:8.4-cli-alpine

# Run each example
docker run --rm -v $(pwd):/app -w /app php:8.4-cli-alpine php functions.php
docker run --rm -v $(pwd):/app -w /app php:8.4-cli-alpine php arguments.php
docker run --rm -v $(pwd):/app -w /app php:8.4-cli-alpine php scope.php
docker run --rm -v $(pwd):/app -w /app php:8.4-cli-alpine php recursion.php
docker run --rm -v $(pwd):/app -w /app php:8.4-cli-alpine php closures.php

Expected Output

Running functions.php:

Hello from a function!
3 + 4 = 7
Now presenting: PHP Functions

Running arguments.php:

Hello, Ada!
Welcome, Grace!
Hello, Linus...
Is it you, Margaret?

Running scope.php:

I live inside the function
I live in the global scope
Call 1: 1
Call 2: 2
Call 3: 3

Running recursion.php:

5! = 120
10! = 3628800
First 10 Fibonacci numbers: 0 1 1 2 3 5 8 13 21 34 

Running closures.php:

Square of 6: 36
4 times 3: 12
Squares: 1, 4, 9, 16, 25
Evens: 2, 4
Double 7: 14
Triple 7: 21

Key Concepts

  • Type declarations are optional but recommended — PHP is dynamically and weakly typed, but adding parameter and return types (with declare(strict_types=1)) catches errors early and documents intent.
  • Default values make parameters optional — give a parameter a default and callers can omit it entirely.
  • Named arguments improve readability — since PHP 8.0 you can pass arguments by name in any order and skip optional ones.
  • Local scope is the default — variables inside a function don’t leak out, and outer variables aren’t visible unless passed in, declared global, or captured with use.
  • static locals persist between calls — useful for counters and memoization without polluting the global scope.
  • Recursion needs a base case — every recursive function must have a stopping condition to avoid infinite loops.
  • Functions are first-class values — store them in variables, pass them to higher-order functions like array_map and array_filter, and return them from other functions.
  • Arrow functions capture scope automaticallyfn () => ... is concise and binds outer variables for you, while full closures use use for explicit capture.

Running Today

All examples can be run using Docker:

docker pull php:8.4-cli-alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining