Functions

Reviewed & published by Brayan K

By the end of this lesson you'll write reusable PHP functions with typed parameters and return types, default and named arguments, variadics, references, and arrow functions and closures — the building blocks of every program bigger than a script.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Defining, Calling & Returning

A function is a named block of code you can run on demand. You define it once with the function keyword, then call it by writing its name followed by (). A parameter is an input slot in the definition; an argument is the actual value you pass when calling. The keyword return hands a value back to whoever called the function — that's different from echo, which only prints. A return type like : string after the ) documents and enforces what comes back; : void means "returns nothing".

<?php
declare(strict_types=1); // turns type mismatches into errors — see Common Errors

// Define once, call as many times as you like.
// 'function' starts a definition; the name follows the same rules as a variable.
function sayHello(): void {        // ': void' means "returns nothing"
    echo "Hello, World!\n";
}

sayHello();                        // call it — runs the body
sayHello();                        // call again — that's the whole point of a function

// A PARAMETER is an input. ': string' after the ) is the RETURN TYPE.
function greet(string $name): string {
    return "Hello, $name!";        // 'return' hands a value back to the caller
}

echo greet("Alice") . "\n";        // prints what greet() returned
echo greet("Bob") . "\n";

// A function that returns a number you can keep using.
function square(int $n): int {
    return $n * $n;
}

$result = square(5);               // store the returned value
echo "5 squared is $result\n";
?>

The declare(strict_types=1); line at the top makes PHP strict about types — pass a string where an int is expected and you get a clear error instead of a silent conversion. Put it at the very top of every file; it's a best-practice habit that catches bugs early.

2️⃣ Typed Parameters, Defaults & Named Arguments

Types in front of a parameter (string $name) say what kind of value is allowed. A default value ($role = "Student") makes a parameter optional — leave it out and the default is used. A nullable type ?string means "a string or null", and a union type int|string (PHP 8) means "either of these". With named arguments (PHP 8) you pass values by parameter name — introduce(role: "Mentor", name: "Carol") — so order stops mattering and your calls read like documentation.

<?php
declare(strict_types=1);

// DEFAULT VALUE: $role is "Student" unless the caller supplies one.
// Parameters with defaults must come AFTER ones without.
function introduce(string $name, string $role = "Student"): string {
    return "$name is a $role";
}

echo introduce("Alice", "Developer") . "\n"; // both arguments given
echo introduce("Bob") . "\n";                 // $role falls back to "Student"

// NULLABLE TYPE: '?string' means "a string OR null".
// UNION TYPE (PHP 8): 'int|string' means "an int OR a string".
function label(int|string $id, ?string $note = null): string {
    $text = "ID: $id";
    if ($note !== null) {          // only add the note when one was passed
        $text .= " ($note)";
    }
    return $text;
}

echo label(42) . "\n";                    // note is null, so it's left off
echo label("A7", "priority") . "\n";      // id is a string here — both types allowed

// NAMED ARGUMENTS (PHP 8): name the parameter, so order no longer matters
// and you can skip optional ones in the middle.
echo introduce(role: "Mentor", name: "Carol") . "\n";
?>

3️⃣ Variadics & Pass-by-Reference

Sometimes you don't know how many arguments you'll get. A variadic parameter int ...$numbers collects every extra argument into one array, so sum(1, 2, 3) and sum(10, 20, 30, 40) both work. By default PHP passes a copy of each argument, so changing a parameter inside a function leaves the caller's variable untouched. Put an ampersand in front — &$total — to pass by reference instead, letting the function edit the original directly. Use references sparingly; usually returning a value is clearer.

<?php
declare(strict_types=1);

// VARIADIC parameter: '...$numbers' collects every extra argument into an array.
function sum(int ...$numbers): int {
    return array_sum($numbers);    // $numbers is a normal array here
}

echo "sum(1, 2, 3) = " . sum(1, 2, 3) . "\n";
echo "sum(10, 20, 30, 40) = " . sum(10, 20, 30, 40) . "\n";
echo "sum() = " . sum() . "\n";    // zero arguments is fine too

// PASS BY REFERENCE: '&$total' lets the function change the caller's variable.
// Without the &, PHP passes a COPY and the original is untouched.
function addTip(float &$total, float $tip): void {
    $total += $tip;                // this edits the caller's $bill directly
}

$bill = 40.0;
addTip($bill, 6.0);                // no '=' needed — $bill is modified in place
echo "Bill after tip: $bill\n";    // 46

// Compare: pass by VALUE (no &) leaves the original alone.
function tryToReset(float $n): void {
    $n = 0;                        // only the local copy changes
}
$score = 99.0;
tryToReset($score);
echo "Score is still: $score\n";   // still 99
?>

4️⃣ Arrow Functions, Closures & Scope

PHP also lets you store a function in a variable — an anonymous function with no name. An arrow function fn($x) => $x * 2 (PHP 7.4+) is the short form: a single expression that automatically grabs any outer variables it mentions. A closure function ($x) use ($y) { ... } can hold a full multi-line body but must explicitly list outer variables in its use () clause. This matters because every function has its own scope: it can't see variables defined outside it unless you pass them in, capture them, or pull a global in with the global keyword.

<?php
declare(strict_types=1);

// ARROW FUNCTION (PHP 7.4+): 'fn(args) => expression'.
// It AUTOMATICALLY captures variables from the surrounding scope.
$factor = 3;
$scale = fn(int $x): int => $x * $factor;   // $factor is captured automatically
echo "scale(5) = " . $scale(5) . "\n";       // 15

// Arrow functions shine as callbacks for array_map / array_filter.
$numbers = [1, 2, 3, 4, 5];
$squares = array_map(fn($n) => $n * $n, $numbers);
echo "squares: [" . implode(", ", $squares) . "]\n";

// CLOSURE (anonymous function): use 'function' and 'use' to capture variables.
// Unlike fn(), a closure does NOT capture automatically — you list what it needs.
$prefix = "Item";
$makeLabel = function (int $i) use ($prefix): string {
    return "$prefix #$i";          // $prefix came from 'use ($prefix)'
};
echo $makeLabel(1) . "\n";
echo $makeLabel(2) . "\n";

// SCOPE & global: a function can't see outer variables unless told to.
$siteName = "LearnCodingFast";
function showSite(): void {
    global $siteName;              // pull the global variable into the function
    echo "Welcome to $siteName\n";
}
showSite();
?>

5️⃣ Your Turn

Now you write the missing pieces. The script below is almost complete — fill in each ___ using the 👉 hint, then run it and check it against the Output panel.

<?php
declare(strict_types=1);
// 🎯 YOUR TURN — fill in each blank marked ___ , then run it.

// 1) Add the RETURN TYPE so this function promises to return an int.
function area(int $w, int $h): ___ {   // 👉 the type is  int
    return $w * $h;
}

// 2) Give $tax a DEFAULT VALUE of 0.2 so it can be left out.
function priceWithTax(float $price, float $tax = ___): float {  // 👉 0.2
    return $price * (1 + $tax);
}

echo "area(4, 5) = " . area(4, 5) . "\n";
echo "priceWithTax(100) = " . priceWithTax(100) . "\n"; // uses the default 0.2

// ✅ Expected output:
//    area(4, 5) = 20
//    priceWithTax(100) = 120
?>

One more — this time finishing an arrow function and a closure that needs to capture a variable.

<?php
declare(strict_types=1);
// 🎯 YOUR TURN — finish the arrow function and the closure.

$numbers = [1, 2, 3, 4];

// 1) Write an ARROW FUNCTION that doubles its argument.
$double = ___ => $n * 2;          // 👉 fn($n)
$doubled = array_map($double, $numbers);
echo "[" . implode(", ", $doubled) . "]\n";

// 2) This closure needs the $unit variable. Capture it with 'use'.
$unit = "kg";
$weigh = function (int $w) ___ : string {   // 👉 use ($unit)
    return "$w$unit";
};
echo $weigh(70) . "\n";

// ✅ Expected output:
//    [2, 4, 6, 8]
//    70kg
?>

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — PHP Functions

FeatureExampleSince
Definitionfunction f($x) { ... }PHP 4
Typed param & returnfunction f(int $x): stringPHP 7.0
Default valuefunction f($x = 10)PHP 4
Nullable type?stringPHP 7.1
Variadicfunction f(int ...$nums)PHP 5.6
By referencefunction f(&$x)PHP 4
Arrow functionfn($x) => $x * 2PHP 7.4
Closurefunction ($x) use ($y) {}PHP 5.3
Union typeint|stringPHP 8.0
Named argumentsf(name: "Carol")PHP 8.0

Mini-Challenge: Shopping-Cart Total

No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments. This combines a variadic function, a return type, and a helper that formats the result.

<?php
declare(strict_types=1);
// 🎯 MINI-CHALLENGE: A shopping-cart total.
// No code is filled in — work from the steps below, then run it.
//
// 1. Write a VARIADIC function 'cartTotal(float ...$prices): float'
//    that returns the sum of every price passed in (hint: array_sum).
// 2. Write a function 'format(float $amount): string' that returns the
//    amount with a leading "$" and 2 decimals (hint: sprintf("$%.2f", $amount)).
// 3. Call cartTotal(9.99, 4.50, 2.00), pass the result to format(),
//    and echo it.
//
// ✅ Expected output:
//    $16.49

// your code here
?>

🎉 Lesson Complete!

Practice quiz

Which keyword hands a value back to the caller of a function?

  • echo
  • print
  • return
  • give

Answer: return. return hands a value back; echo only prints and does not return anything usable.

What does the return type : void mean?

  • The function returns nothing
  • Returns an empty string
  • Returns null only
  • The function never ends

Answer: The function returns nothing. : void declares that the function returns no value.

What does a function return if it reaches the end without a return statement?

  • 0
  • An empty string
  • It throws an error
  • null

Answer: null. A function with no return (or a bare return;) gives back null.

What does the nullable type ?string mean for a parameter?

  • A string that cannot be null
  • A string OR null
  • An array of strings
  • An optional integer

Answer: A string OR null. ?string means the value may be a string or null.

What does a variadic parameter like int ...$numbers do?

  • Collects all extra arguments into an array
  • Requires exactly one argument
  • Passes by reference
  • Makes the parameter optional only

Answer: Collects all extra arguments into an array. A variadic parameter gathers every extra argument into a single array.

By default, how does PHP pass arguments to a function?

  • By reference
  • As global variables
  • By value (a copy)
  • As constants

Answer: By value (a copy). PHP passes a copy by default, so editing a parameter doesn't change the caller's variable.

What does the & in a parameter like &$total enable?

  • A default value
  • Pass by reference, so the function can change the original
  • A union type
  • A variadic

Answer: Pass by reference, so the function can change the original. An ampersand passes by reference, letting the function modify the caller's variable in place.

How does an arrow function fn($x) => $x * $factor capture $factor?

  • It cannot use outer variables
  • Only via a use() clause
  • Only if it is global
  • Automatically from the surrounding scope

Answer: Automatically from the surrounding scope. Arrow functions automatically capture variables from the enclosing scope.

How does a closure (function ($x) { ... }) capture an outer variable?

  • Automatically
  • By listing it in a use () clause
  • It cannot
  • By marking it readonly

Answer: By listing it in a use () clause. A closure must explicitly list captured variables in its use () clause.

What lets named arguments like introduce(role: "Mentor", name: "Carol") work?

  • They must be in declaration order
  • They require references
  • Order no longer matters because you pass by parameter name
  • They only work for one argument

Answer: Order no longer matters because you pass by parameter name. Named arguments (PHP 8) pass by parameter name, so the order stops mattering.

Continue this course

Frequently asked questions

What is the difference between an arrow function and a closure in PHP?

Both create anonymous (unnamed) functions, but they capture outer variables differently. An arrow function — fn($x) => $x * $factor — automatically captures variables from the surrounding scope, and its body must be a single expression. A closure — function ($x) use ($factor) { ... } — does not capture automatically: you list each variable you need in the use () clause, but in return it can hold a full multi-line body. Use an arrow function for short one-liners and a closure when you need more code or want to be explicit about what you capture.

When should I pass a parameter by reference with &?

By default PHP passes a copy of each argument, so changing a parameter inside a function does not affect the caller's variable. Add an ampersand — function f(&$x) — when you genuinely want the function to modify the original variable in place, such as sorting an array or updating a running total. Most of the time you should instead return a new value and let the caller assign it; references make code harder to follow and should be used sparingly.

Why does my function return null when I expected a value?

A PHP function returns null whenever it reaches the end without hitting a return statement, or hits a bare return; with no value. The two usual causes are forgetting return entirely (you echo'd the value inside the function instead of returning it) or a return that sits inside an if branch that did not run. Add a return type like : int — with declare(strict_types=1) PHP will then raise a TypeError if the function ever falls through to null, turning a silent bug into a clear error.

What are named arguments and when are they useful?

Named arguments (PHP 8.0+) let you pass values by parameter name instead of position — introduce(name: 'Carol', role: 'Mentor'). They make calls self-documenting and let you skip optional parameters in the middle without passing every default before the one you care about. They are especially helpful for functions with several optional parameters, where a bare true, false, null at the call site tells the reader nothing.

How do I use a variable from outside inside a function?

Functions have their own local scope, so they cannot see outer variables by default. The cleanest fix is to pass the value in as a parameter. If you truly need a program-wide value, declare global $name; inside the function to pull in the global variable, or use a static variable to remember a value between calls. Prefer parameters and return values — heavy use of global makes functions hard to test and reason about.