Functions of your own
Naming a piece of work once, parameters and their defaults, return types, and the strict mode that turns quiet conversions into loud mistakes.
A function is a name for a piece of work
A function takes the steps you were about to repeat, gives them a name, and lets the name carry a meaning: function totalWithTax(float $price, int $quantity): float { … }. The reasons are the same in every language — the work happens once, the name explains it, and a fix happens in one place — but PHP adds a practical one: the language's own vocabulary is functions, so writing your own keeps every call site reading at the same level.
Parameters are declared with the types they accept, and the return type after the colon. PHP checks those types at the call and honours them at the return, refusing the wrong shapes with an error. That is a change from the language's reputation — modern PHP is typed, and the typing is the documentation.
Defaults, and strict mode
A parameter can carry a default — function greet(string $name, string $greeting = "Hello") — and callers may then leave it out. Defaults can only follow required parameters, and the default is a constant expression, not a piece of logic: reach for an if inside when the value needs thought.
declare(strict_types=1); as the first statement of the file changes the rules at the call site: a function asking for int no longer accepts the string "3" just because it looks like one. It does not make the language typed — it stops the conversions from happening silently, which is the part that hides bugs. New files should start with it.
PHP 8 also lets a caller name the argument it is skipping: totalWithTax(price: 19.99, quantity: 3, tax: 0.05). The names come from the function signature, so renaming a parameter is a breaking change for any caller that used the name. That is a reason to choose parameter names as carefully as function names, and to use named arguments mainly when a call has several optional values.
Functions as values
A function is a value: it can be stored in a variable, passed to another function and returned — which is what every array_map in the previous chapter was doing with its arrow. fn ($price) => round($price, 2) is a function without a name, and a named one becomes such a value by its name, as a string or with the strtoupper(...) callable syntax.
That is the last piece the classes chapter builds on: methods are functions attached to a value, and the same rules — parameters, types, return — carry over unchanged.
Typed, defaulted, returned
<?php
declare(strict_types=1);
function totalWithTax(float $price, int $quantity, float $tax = 0.2): float
{
return round($price * $quantity * (1 + $tax), 2);
}
echo totalWithTax(19.99, 3) . PHP_EOL; // default tax
echo totalWithTax(19.99, 3, 0.05) . PHP_EOL; // reduced rate
// A function that returns several values returns an array.
function splitBill(float $total, int $people): array
{
$each = intdiv((int) round($total * 100), $people) / 100;
$rounded = $each * $people;
return ['each' => $each, 'remainder' => round($total - $rounded, 2)];
}
$bill = splitBill(100.0, 3);
echo 'Each pays: ' . number_format($bill['each'], 2) . PHP_EOL;
echo 'Left over: ' . number_format($bill['remainder'], 2) . PHP_EOL;
// A function as a value, the same shape array_map takes.
$double = fn ($n) => $n * 2;
echo implode(', ', array_map($double, [1, 2, 3])) . PHP_EOL;A small invoice builder
Run it and read the invoice. Then add a `discount()` function with a default percentage, use it before tax, and add a fourth line with a quantity of your own.
With strict_types on, totalWithTax("19.99", 3) is an error, not a quiet success. That is the point: the mistake is reported at the call that made it, instead of turning into a wrong number three functions later.
Calls, scope and returns
Calling a function creates a fresh scope. Variables assigned inside it are local and disappear when it returns; a variable in the caller with the same name is a different variable. Parameters are passed by value by default, so changing a parameter does not change the caller’s variable — objects are the exception, because the value being passed is a handle to the same instance.
return ends the call immediately and hands a value back; a function with no return hands back null. A declared parameter type is checked where the argument enters, and a return type where the value leaves. declare(strict_types=1) changes those checks from “convert if it looks close” to “refuse anything that is not the declared type”, and it must be the first statement in the file.
A function that returns nothing still returns. The void return type says “do not use this value”, and a bare return; is the early exit from the middle of a function. Early returns are often clearer than a deep if: check the impossible case at the top, leave, and let the rest of the body read without a level of indentation.
Three function mistakes hide in plain sight: forgetting return and getting null, putting declare(strict_types=1) after a line of code, and expecting a function to change a variable from the caller’s scope. Return the value you computed, declare strict types first, and pass what the function needs as a parameter.
Try these next
- Write
money(float $amount): stringand call it with an integer; then turn on strict types and try the same call. - Add a
$tax = 0.2default to a total function and call it with and without the third argument. - Return an array of two values from a split function and print both fields by name.
A shipping quote
Run the three quotes, then add a next-day zone with its own rate. Give `shipping()` a default base cost and call it without that argument once.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What do the types in function total(float $price): float do?
