Skip to main content

    Lesson 6 • Intermediate

    Functions ⚡

    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.

    What You'll Learn in This Lesson

    • Define and call functions, and return values with return
    • Type parameters and return values: int, ?string, array, void, and union int|string
    • Give parameters default values and call with PHP 8 named arguments
    • Accept any number of arguments with a variadic ...$args
    • Modify a caller's variable with pass-by-reference &$x
    • Write arrow functions fn() => and closures with use, and handle scope with global

    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".

    Define once, call many times
    <?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";
    ?>
    Output
    Hello, World!
    Hello, World!
    Hello, Alice!
    Hello, Bob!
    5 squared is 25
    This is real code — run it for free atonecompiler.com/phpor in your own editor.

    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.

    Defaults, nullable & union types, named arguments
    <?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";
    ?>
    Output
    Alice is a Developer
    Bob is a Student
    ID: 42
    ID: A7 (priority)
    Carol is a Mentor
    This is real code — run it for free atonecompiler.com/phpor in your own editor.

    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.

    Variadic ...$args and references &$x
    <?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
    ?>
    Output
    sum(1, 2, 3) = 6
    sum(10, 20, 30, 40) = 100
    sum() = 0
    Bill after tip: 46
    Score is still: 99
    This is real code — run it for free atonecompiler.com/phpor in your own editor.

    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.

    Arrow functions, closures with use, and scope
    <?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();
    ?>
    Output
    scale(5) = 15
    squares: [1, 4, 9, 16, 25]
    Item #1
    Item #2
    Welcome to LearnCodingFast
    This is real code — run it for free atonecompiler.com/phpor in your own editor.

    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.

    🎯 Your turn: return type & default value
    <?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
    ?>
    Output
    area(4, 5) = 20
    priceWithTax(100) = 120
    Fill the first ___ with the type int and the second with the default 0.2, then run it.

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

    🎯 Your turn: arrow function & closure capture
    <?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
    ?>
    Output
    [2, 4, 6, 8]
    70kg
    Write fn($n) for the first blank and use ($unit) for the second so the closure can see $unit.

    Common Errors (and the fix)

    • Your function returns null instead of a value — you forgot return, or you echo'd the value inside the function instead of returning it. A function that never hits a return gives back null. Add a return type like : int so PHP flags the mistake instead of failing silently.
    • "TypeError: ... must be of type int, string given" — with declare(strict_types=1) at the top, passing the wrong type is an error, not a quiet conversion. Pass the right type, or change the parameter to a union like int|string if both are genuinely valid.
    • "Undefined variable" inside a function — functions have their own scope and can't see outer variables. Pass the value in as a parameter, or use global $name; for a program-wide value. Variables defined outside simply don't exist inside unless you bring them in.
    • Your change inside a function "didn't stick" — PHP passes arguments by value (a copy) by default, so editing a parameter doesn't touch the caller's variable. If you really need to modify the original, declare the parameter by reference with &$x — otherwise return the new value and assign it.
    • "Cannot use positional argument after named argument" — once you start naming arguments in a call, every following argument must also be named. Either name them all, or keep the unnamed ones first: f("Carol", role: "Mentor").

    Pro Tips

    • 💡 Always add type hints and a return type. They catch bugs early and act as built-in documentation — the reader knows exactly what goes in and what comes out.
    • 💡 Prefer parameters and return values over global and references. Functions that only touch their inputs and outputs are far easier to test and reason about.
    • 💡 Reach for fn() for one-line callbacks in array_map/array_filter, and a full closure with use when the body needs more than one expression.

    📋 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

    Frequently Asked Questions

    Q: 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.

    Q: 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.

    Q: 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.

    Q: 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.

    Q: 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.

    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.

    🎯 Mini-Challenge: total and format a cart
    <?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
    ?>
    Write a variadic cartTotal(...) and a format() helper, then echo the formatted total. Expected output: $16.49.

    🎉 Lesson Complete!

    • ✅ Define a function with function, call it with (), and hand back a value with return
    • ✅ Type parameters and returns — int, ?string, array, void, and union int|string
    • ✅ Make parameters optional with defaults and call clearly with PHP 8 named arguments
    • ✅ Accept any number of inputs with a variadic ...$args
    • ✅ Edit a caller's variable with pass-by-reference &$x (and know when not to)
    • ✅ Store functions in variables with fn() => and use closures, and manage scope with global
    • Next lesson: Arrays & Collections — store and process lists of values, PHP's most-used data structure

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service

    Install LearnCodingFast

    Learn faster with the app on your home screen.