Functions
A function is a named unit of behavior. It accepts input, performs one defined job, and can return a result to the code that called it.
Functions are not only a way to avoid duplicated lines. A useful function gives a rule a name and a boundary. calculateTaxCents() communicates more than the multiplication inside it, and formatCustomerName() tells a reader why trimming and joining text are happening.
Use a function when a piece of logic has a clear purpose, is called more than once, needs focused testing, or would make the surrounding script easier to read.
Declare And Call A Function
A named function starts with function, followed by its name, parameter list, optional return type, and body:
<?php
function formatFullName(string $firstName, string $lastName): string
{
$cleanFirstName = trim($firstName);
$cleanLastName = trim($lastName);
return "$cleanFirstName $cleanLastName";
}
$fullName = formatFullName(' Ada ', ' Lovelace ');
echo $fullName, PHP_EOL;
Output:
Ada Lovelace
Declaring the function defines the behavior. Calling formatFullName(...) runs it. Parentheses are required for the call, even when a function has no parameters.
Execution enters the function with values assigned to its parameters. When PHP reaches return, execution leaves the function and the returned value replaces the call expression. In the example, the returned string becomes the value assigned to $fullName.
Parameters And Arguments Are Different
Parameters are the input names in the declaration:
function formatFullName(string $firstName, string $lastName): string
Arguments are the values supplied by a particular call:
formatFullName('Ada', 'Lovelace');
Here, $firstName and $lastName are parameters. 'Ada' and 'Lovelace' are arguments.
That distinction matters when debugging. "The $lastName parameter expects a string" describes the function contract. "The second argument was 'Lovelace'" describes one call.
By default, ordinary arguments are passed by value. A function works with its parameter value without changing the caller's variable:
<?php
function normaliseCode(string $code): string
{
$code = strtoupper(trim($code));
return $code;
}
$code = ' ab-12 ';
$normalisedCode = normaliseCode($code);
var_dump($code);
var_dump($normalisedCode);
Output:
string(7) " ab-12 "
string(5) "AB-12"
PHP also supports pass-by-reference parameters, but they create an intentional mutation of caller-owned data. They are deferred until references are covered explicitly. Returning a transformed value is clearer for most beginner functions.
A Signature Describes The Contract
This signature says the function requires two strings and promises a string result:
function formatFullName(string $firstName, string $lastName): string
Parameter names should describe meaning, not merely type. $subtotalCents is better than $number because it records both purpose and unit. $ratePercent is clearer than $rate when callers must pass 20 rather than 0.20.
Basic parameter and return declarations make examples easier to understand and help PHP and development tools detect mismatched values. A later lesson covers coercion, nullable types, void, unions, and strict_types in detail. For now, make simple function contracts visible.
Return A Value Instead Of Printing It
A calculation or formatter should usually return its result:
<?php
function formatPrice(int $cents): string
{
return 'GBP ' . number_format($cents / 100, 2);
}
$basketLabel = formatPrice(1299);
$emailLabel = formatPrice(1299);
echo "Basket: $basketLabel\n";
echo "Email: $emailLabel\n";
formatPrice() decides how a price looks. It does not decide whether that text belongs in a command-line message, an HTML page, an email, or a test assertion.
A function that echoes directly combines calculation with output. Sometimes output is the function's actual job, but business rules and formatting functions are more reusable when the caller controls the destination.
Do not confuse return with echo:
returnsends a value to the caller and leaves the function;echowrites output and then execution continues;- assigning a function call stores its returned value;
- ignoring a return value discards it.
return Ends The Current Function Call
Statements after an executed return are unreachable:
<?php
function shippingMessage(bool $isPaid): string
{
if (!$isPaid) {
return 'Waiting for payment';
}
return 'Ready to ship';
}
echo shippingMessage(false), PHP_EOL;
The first return handles the unpaid path immediately. If $isPaid is true, that branch is skipped and execution reaches the second return.
This early-return pattern is useful when one condition prevents the main result. Each possible path must still return the type promised by the signature. A function declared : string should not accidentally reach its closing brace without returning a string.
Exceptions provide another way for a function to stop when it cannot complete its contract, but throwing and catching them belongs to the later errors and exceptions lesson.
Use Default Values For Genuinely Optional Input
A default value lets a caller omit an optional argument:
<?php
function buildGreeting(string $name, string $prefix = 'Hello'): string
{
return "$prefix, $name";
}
echo buildGreeting('Mina'), PHP_EOL;
echo buildGreeting('Mina', 'Welcome back'), PHP_EOL;
Output:
Hello, Mina
Welcome back, Mina
The default is used only when the argument is omitted. Passing another value replaces it. Passing null does not mean "use the default" and would not satisfy this parameter's string declaration.
Put required parameters before optional parameters:
function buildGreeting(string $name, string $prefix = 'Hello'): string
Putting a required parameter after one with a default prevents that earlier argument from being omitted positionally. In common forms, PHP treats the supposedly optional parameter as required and deprecates the declaration. Keep signatures ordered from required inputs to optional choices.
Defaults should represent unsurprising convenience, not a hidden business decision. A default greeting prefix is visible and low risk. A silently selected tax jurisdiction or permission level would deserve an explicit argument.
Positional And Named Arguments
The calls so far use argument position:
buildGreeting('Mina', 'Welcome back');
PHP 8 and later also support named arguments:
buildGreeting(name: 'Mina', prefix: 'Welcome back');
Named arguments can make calls with several similar values easier to read, and they can skip optional parameters. Positional arguments must come before named arguments when both styles are combined.
Use named arguments deliberately. Parameter names become part of how callers invoke the function, so renaming a public parameter can break named calls even when the value types and order stay the same. For short project-local calls, positional arguments are often sufficient.
Compose Small Functions
One function can use another function's returned value:
<?php
function calculateTaxCents(int $subtotalCents, int $ratePercent): int
{
return (int) round($subtotalCents * $ratePercent / 100);
}
function calculateTotalCents(int $subtotalCents, int $ratePercent): int
{
$taxCents = calculateTaxCents($subtotalCents, $ratePercent);
return $subtotalCents + $taxCents;
}
echo calculateTotalCents(2500, 20), PHP_EOL;
Output:
3000
calculateTotalCents() delegates one named rule rather than repeating the tax formula. This is useful when the extracted function represents a real concept and can be tested independently.
Do not split every operator into a separate function. Extraction should improve the vocabulary or remove meaningful duplication, not force a reader to jump through many files to understand one line.
Keep One Clear Responsibility
A focused function should be explainable with one short sentence:
formatFullName()cleans and joins two name parts;calculateTaxCents()calculates tax in cents;isPaidOrder()answers one yes-or-no question.
A function that validates a form, saves a database row, sends an email, and prints HTML has several reasons to change. It is difficult to reuse and difficult to verify without triggering all of its effects.
At this stage, prefer functions that calculate, classify, or format and then return a result. These are predictable: the same arguments produce the same result, and the caller can inspect the return value directly.
Name Functions For Their Result Or Action
Use a verb that reveals the job:
formatPrice()returns formatted text;calculateTotalCents()returns a calculation;isEligibleForDiscount()returns a boolean decision;printReceipt()would perform output as its named action.
Avoid vague names such as process(), handleData(), doThing(), or runLogic(). They hide the rule a reader is trying to find.
Call functions with the same spelling and casing used in their declarations. PHP's ASCII function names are case-insensitive, but relying on that behavior makes searches and reviews less reliable. Also avoid declaring a name already used by another function in the same namespace; PHP cannot redeclare a function.
Test Boundaries, Not Only One Happy Example
A function call is easy to repeat with controlled values. For a shipping threshold function, useful checks include an amount below the threshold, exactly at it, and above it. For a formatter, try surrounding whitespace and already-clean input.
Write the expected result before running the code:
| Call | Expected result |
|---|---|
calculateTaxCents(2500, 20) |
500 |
calculateTaxCents(1999, 20) |
400 after rounding |
calculateTaxCents(2500, 0) |
0 |
One passing example proves only one path. Boundary checks expose incorrect comparison operators, percentage conventions, rounding assumptions, and missing return paths.
Common Function Mistakes
| Symptom | Likely cause |
|---|---|
| Function prints when a value is needed | Used echo instead of return |
Missing result or TypeError |
Function reached the end without returning the promised value |
| Later statements never run | An earlier return already left the function |
| Arguments affect the wrong inputs | Positional order does not match the parameter order |
| Optional argument cannot be omitted | Optional parameter was placed before a required one |
| Call is hard to understand | Function or parameter names do not describe purpose and units |
| Small change requires editing copies | Repeated rule has not been given one function |
| Function is difficult to test | It combines calculation with unrelated side effects |
| Named call breaks after refactoring | A parameter name used by callers was changed |
What You Should Be Able To Do
After this lesson, you should be able to declare and call a named function, distinguish parameters from arguments, read a basic function signature as a contract, return a value to the caller, explain why return ends the current call, use an early return for a simple branch, put required parameters before defaults, recognise positional and named calls, compose small functions, choose names that reveal purpose and units, and test more than one input path.
The next lesson examines variable scope, globals, static variables, closures, and captured values. Error handling and detailed type behavior are covered separately so each boundary is introduced with enough context.
Official references: PHP's manual pages for user-defined functions, function parameters and arguments, and returning values.
Practice
Task: Format Full Name
Task
Write a function named formatFullName() with this signature:
function formatFullName(string $firstName, string $lastName): string
The function should:
- trim whitespace from both name arguments;
- join the cleaned values with exactly one space;
- return the full name;
- contain no
echostatement.
Call it with both sets of arguments below and print each returned value outside the function:
formatFullName(' Ada ', ' Lovelace ');
formatFullName('Grace', 'Hopper');
The exact output should be:
Ada Lovelace
Grace Hopper
Hints
- Store the result of each
trim()call in a clearly named local variable. - Build the final string after both values have been cleaned.
- The caller can pass the function call directly to
echo, or assign the return value first.
Show solution
Solution
<?php
function formatFullName(string $firstName, string $lastName): string
{
$cleanFirstName = trim($firstName);
$cleanLastName = trim($lastName);
return "$cleanFirstName $cleanLastName";
}
$firstFullName = formatFullName(' Ada ', ' Lovelace ');
$secondFullName = formatFullName('Grace', 'Hopper');
echo $firstFullName, PHP_EOL;
echo $secondFullName, PHP_EOL;
// Prints:
// Ada Lovelace
// Grace Hopper
Explanation
The declaration names two string parameters and promises a string result. Each call supplies different arguments, but both calls run the same cleaning and joining rule.
formatFullName() returns its result instead of choosing where to display it. The caller stores each returned string and prints it. The same function could therefore be used by a template, an email builder, or a test without capturing output from inside the function.
The second call is useful even though its arguments have no surrounding spaces. It confirms that the formatter also preserves already-clean input and does not depend on whitespace being present.
Task: Predict Default And Named Arguments
Task
Before running this code, predict exactly what each line prints:
<?php
function buildGreeting(string $name, string $prefix = 'Hello'): string
{
return "$prefix, $name";
}
echo buildGreeting('Mina'), PHP_EOL;
echo buildGreeting('Mina', 'Welcome back'), PHP_EOL;
echo buildGreeting(prefix: 'Hi', name: 'Omar'), PHP_EOL;
Write down:
- which call uses the default value;
- which call replaces the default positionally;
- how PHP assigns the arguments in the final call;
- the three output lines in their exact order.
Then run the unchanged code and compare the output with your prediction.
Hints
- A default is used only when its argument is omitted.
- In the second call, argument position maps
'Mina'to$nameand'Welcome back'to$prefix. - Named arguments are matched by parameter name, not by the order in which they are written.
Show solution
Solution
<?php
function buildGreeting(string $name, string $prefix = 'Hello'): string
{
return "$prefix, $name";
}
echo buildGreeting('Mina'), PHP_EOL;
echo buildGreeting('Mina', 'Welcome back'), PHP_EOL;
echo buildGreeting(prefix: 'Hi', name: 'Omar'), PHP_EOL;
// Prints:
// Hello, Mina
// Welcome back, Mina
// Hi, Omar
Explanation
The first call supplies only the required $name argument, so PHP assigns the declared default Hello to $prefix.
The second call is positional. The first argument goes to $name, and the second goes to $prefix, replacing the default with Welcome back.
The final call names both target parameters. Although prefix appears before name in the call, PHP assigns 'Hi' to $prefix and 'Omar' to $name. Named argument order does not change the function's parameter meanings.
All three calls receive a returned string. The surrounding echo statements, not buildGreeting(), decide when those strings are written to output.
Task: Write Tax Function
Task
Write a function named calculateTaxCents() with this signature:
function calculateTaxCents(int $subtotalCents, int $ratePercent = 20): int
For this exercise, assume the caller has already validated that both arguments are non-negative.
The function should:
- return
0immediately when the subtotal or rate is0; - otherwise multiply the subtotal by the percentage and divide by
100; - round the result with
round(); - cast the rounded result to
intand return it; - contain no output statements.
Call it with these arguments:
calculateTaxCents(2500);
calculateTaxCents(1999);
calculateTaxCents(2500, 0);
Print each returned value. The exact output should be:
500
400
0
Hints
- The first two calls omit
$ratePercent, so they use the default20. - Use
||in the early-return condition. 1999 * 20 / 100is399.8, which rounds to400.- Invalid input policies and exceptions are covered in a later lesson.
Show solution
Solution
<?php
function calculateTaxCents(int $subtotalCents, int $ratePercent = 20): int
{
if ($subtotalCents === 0 || $ratePercent === 0) {
return 0;
}
return (int) round($subtotalCents * $ratePercent / 100);
}
echo calculateTaxCents(2500), PHP_EOL;
echo calculateTaxCents(1999), PHP_EOL;
echo calculateTaxCents(2500, 0), PHP_EOL;
// Prints:
// 500
// 400
// 0
Explanation
The signature records the unit convention: the subtotal is in cents and the rate is a whole percentage. A caller passes 20 for 20 percent, not 0.20. The default makes 20 percent available when the second argument is omitted.
The first branch is an early return. If either factor is zero, the tax must be zero, so the function can finish without evaluating the remaining formula.
The normal path calculates the percentage, rounds fractional cents, casts the result to the promised integer type, and returns it. The 1999 case checks the rounding boundary rather than only using a subtotal that produces an exact whole-cent result.
This function assumes validated non-negative input because the exercise is about function calls and return paths. The later errors and exceptions lesson defines how a function should reject values that violate its contract.