×
Author:
Website:
Page title:
URL:
Published:
Last revised:
Accessed:

User-defined Functions

Overview

A function is a block of program code that performs a specific task, and can be called from any point in a program. Some programming and scripting languages draw a distinction between a function (which can return a value to the program statement that calls it) and a procedure (or sub-routine) which does not directly return a value. A function can accept arguments, and can return a value to the routine that calls it (although it does not actually have to do either).

PHP has hundreds of built-in functions, all of which are documented on the PHP Group website (http://www.php.net). We can use PHP's built-in functions to carry out many common tasks, such as manipulating strings, searching and sorting arrays, storing and keeping track of date and time information, and carrying out complex mathematical calculations. That means we don't have to write the code needed to carry out these basic tasks ourselves, and can instead concentrate on implementing the logic for our application.

We will of course still have a lot of code to write if we are developing an application of any significance. There will be times when we need to create our own specialised code to handle application-specific tasks. Many of these tasks may need to be carried out repeatedly, as our application constantly receives new input data for processing. That being the case, we should consider writing our own (user-defined) functions to handle these repetitive tasks.

Functions are important building blocks for creating reusable and efficient code. Once we have created a function to handle a particular situation, we can call on that function whenever that situation arises. The only things that will change each time the function is called are the arguments passed to the function and the function's return value.

In this article we'll look at how to define and call a function, explain some of the terminology used, and provide numerous examples of how we can create and use functions in our code. We'll be exploring concepts such as optional parameters and default arguments, function variables, nested and recursive functions, and anonymous functions. We'll start by looking at how to define a function.

Defining a function

Most user-defined functions require a name, a parameter list, and a block of code that will be executed each time the function is called. We define a function using the function keyword. This is immediately followed by a list of comma-separated parameters within parentheses (brackets). The code to be executed follows, enclosed within braces (curly brackets). Here is the basic syntax:

function functionName(parameter list) {
code to be executed;
}

Function names should reflect the purpose of the function, but should not be too long. A function name may start with a letter or an underscore character, and can only contain alphanumeric characters and underscores. It may not start with a number, and cannot contain spaces. The following example defines a simple function that has no formal parameters and does not return a value when called. It simply outputs some text:

<?php
// The function definition
function helloWorld() {
echo "Hello World!";
}
.
.
.
// The function call
helloWorld();
// Outputs: "Hello World!"
?>

We could of course obtain the same result with far less code. Writing a function to simply output some text is creating unnecessary work for ourselves. It is also inefficient from a performance point of view, because a function call invariably involves more processor instructions than (for example) a simple echo command.

Functions become far more useful when they allow us to use the same code to handle repetitive tasks. For example, suppose we are working on an order-processing application. Each order consists of some number of order items, and each order item includes the quantity and price of each item ordered.

We could have hundreds of orders to process, each of which could contain any number of order items. Calculating the total price of each order item is thus a highly repetitive task for which a function is ideally suited. We could do something like this:

<?php
function getItemTotal($quantity, $price) {
return $quantity * $price;
}
.
.
.
$qty = 15;
$price = 2.75;
$itemTotal = getItemTotal($qty, $price);
echo "Item total: €$itemTotal";
// Item total: €41.25
?>

In this example, the getItemTotal() function has two parameters ($quantity and $price). This means that it requires two arguments (we'll talk about the difference between parameters and arguments in more detail shortly). Note that the name of each parameter in the parameter list must begin with the dollar sign ($), as for PHP variables.

The value returned by the getItemTotal() function (i.e. its return value) is the result of multiplying the $quantity and $price arguments together. This implies (but does not guarantee) that the arguments passed to the function will be numeric values.

In a real-world application, we would probably use an object to represent each order, an indexed array to hold the order items, and an associative array to hold the quantity and price of each individual order item, but that's getting a bit ahead of ourselves. Hopefully you can see how useful the getItemTotal() function will be if we need to process a large number of orders.

Note that, while the body of a function may contain just about any legitimate PHP code, including nested functions and loops, a function should ideally be designed to perform one specific task, and one task only. This approach makes the code easier to read and maintain, and enhances code re-usability.

To put things into perspective, a function that calculates the product of two numbers and returns the square root of the result does not have a great number of use cases. We are better off creating two separate functions - one to calculate the product of two numbers, and another to calculate the square root of a number. We can still use these functions in tandem if required, but we can also use each function independently, which gives us more flexibility.

Before we proceed, we should mention a piece of terminology that is frequently used but not always fully explained in sources related to functions and their use, namely the function signature. In broad terms, a function signature uniquely identifies the function, defines the input values (or arguments) expected by the function, and stipulates the type of value the function should return. The function signature typically includes the following features:

  • A unique function name
  • A list of parameters and their types
  • A return value and its type

A couple of things to note here. First of all, PHP is a loosely typed language meaning that a variable's type is normally determined by PHP according to the value assigned to it. The same principle applies to function definitions. We can explicitly require each of the function's parameters to be of a certain type, but we do not have to do so. The same is true for the function's return value.

The other thing to note is that PHP functions always have global scope, even if they are declared inside other functions. This means that function names must be unique. In some programming languages (C and C++ for example) functions can be overloaded. This means that two functions can have the same name, but differ in the number and type of the arguments they take. The overloading of functions is not allowed in PHP.

Calling a function

If a function is declared in the global namespace, we can call that function from anywhere in a script or program, regardless of whether the function call appears before or after the function definition in our code. This is possible because the PHP pre-processor reads the program code looking for (among other things) function definitions before the script or program is executed. For example:

<?php
$result = getProduct(5, 16);
echo $result;
.
.
.
function getProduct($x, $y) {
return $x * $y;
}
// 80
?>

The only exception is a situation in which a function is defined within the context of a conditional statement. A function defined within the body of an if statement, for example, is only processed if the if statement's conditional expression evaluates to true, which can only be determined at run time. For example:

<?php
$result = getProduct(5, 16);
echo $result;
.
.
.
if(true) {
function getProduct($x, $y) {
return $x * $y;
}
}
// Fatal error: Uncaught Error: Call to undefined function getProduct() . . .
?>

In order to be able to use the getProduct() function, it must be called after the if statement appears in our code, as per the following example:

<?php
if(true) {
function getProduct($x, $y) {
return $x * $y;
}
}
.
.
.
$result = getProduct(5, 16);
echo $result;
// 80
?>

Needless to say, if the if statement's conditional expression evaluates to false, calling the function will generate an error regardless of where the function call occurs in our script. For that reason, it is generally considered poor programming practice to declare a function inside a conditional statement unless you absolutely have to - and it's difficult to think of a good reason for doing so!

One important thing to note from the above example is that we have two function parameters (the variables $x and $y). This means that the function expects two arguments (arguments are the values or variables we actually supply to the function when we call it). If we supply fewer then the expected number of arguments, we are going to have a problem. For example:

<?php
function getProduct($x, $y) {
return $x * $y;
}
.
.
.
$result = getProduct(10);
echo $result;
// Fatal error: Uncaught ArgumentCountError: Too few arguments . . .
?>

The arguments passed to a function are matched to the function's parameters in the order in which they arrive. If we supply too many arguments to a function, it will take each argument in turn and match it to the parameter with the corresponding position in the parameter list. The first argument is matched to the first parameter, the second argument is matched to the second parameter, and so on. Any arguments that do not have a corresponding parameter are simply ignored. For example:

<?php
function getProduct($x, $y) {
return $x * $y;
}
.
.
.
$result = getProduct(5, 15, 25);
echo $result;
// 75
?>

We have not explicitly stated, in our examples so far, the type of argument expected for each parameter. PHP infers that the arguments are numeric values from the nature of the operation carried out, i.e. multiplication. If the arguments are integers or floating-point values, or even if they are strings that can be interpreted as numbers, the function will work. For example:

<?php
function getProduct($x, $y) {
return $x * $y;
}
.
.
.
$result = getProduct("7", "12");
echo $result;
// 84
?>

Assuming that we have passed the minimum number of arguments to the getProduct() function, what happens if either of the arguments is both non-numeric and a value that cannot be converted to a number? Let's find out:

<?php
function getProduct($x, $y) {
return $x * $y;
}
.
.
.
$result = getProduct(6, "seven");
echo $result;
// Fatal error: Uncaught TypeError: Unsupported operand types: int * string . . .
?>

In this example, the function tries to multiply an integer value by a string value that cannot be interpreted as a numeric value, and so a fatal error occurs. The function happily accepts the second argument simply because it is expecting two arguments, regardless of type. It is the multiplication operand (*) that causes the problem here, because it requires both of its operands to be numeric values.

You have probably realised by now that a function call consists of the name of the function, followed by a parenthesised and comma-delimited list of values or variables being passed to the function as arguments. The function call typically forms part of a statement that assigns the function's return value to a variable. For example:

$variable = functionName(arg_1, arg_2, ... arg_n);

As you may have noticed from previous examples, the function name is always followed by parentheses in the function definition, even if there are no parameters and the parentheses are empty. Similarly, even if a function doesn't require any arguments, the function call must include the empty parentheses in order to be recognised as a function call.

Function parameters

We can think of function parameters as placeholders for the arguments (i.e. the actual values) a function expects to receive when it is called. For example, the definition for a function that requires exactly two arguments will include two parameters in its parameter list.

Each parameter has a unique name, which must start with the dollar sign ($). The naming conventions for function parameters are essentially the same as those for PHP variables, except that it is permissible to use reserved words as parameter names.

As we have already seen, a function definition starts with the function keyword, followed by the function name, which is followed in turn by a parenthesised and comma-separated list of parameters. The parameter names can be referenced within the body of the function as if they were local variables, which is essentially what they are.

The value and type of each of these "local variables" will depend in part on the value and type of the arguments received by the function when it is called. The types of argument that can be passed to a function as arguments include scalar values, arrays, null, or objects.

By default, scalar variables and array variables are passed to a function by value, meaning that the function receives a copy of the variable, and any changes to the copy do not affect the value of the variable itself. Objects, on the other hand, are usually passed to a function by reference, which means that changes made to the object's properties within the function are changes to the object itself.

PHP also makes assumptions about the type of each argument received based on how we attempt to use the local variable corresponding to that argument within the function body. If the argument's type does not match these assumptions, PHP will attempt to convert it to the type expected. If the variable cannot be converted to the type required, a fatal error is generated.

In the examples we have seen so far, we have not specified a datatype for any of the parameters in the parameter list. This is in keeping with the nature of PHP as a weakly-typed language that can interpret variables as one type or another based on the context in which they are used. Sometimes, however, it would be useful to be able to specify the type of argument we expect to receive for each parameter.

We have also seen that the arguments passed to a function are matched to the function's parameters based on their position in the function call's argument list. We can modify this behaviour using named arguments in the function call so that an argument is matched to a function parameter based on its name rather than its position in the argument list.

Typed parameters

In the functions we have seen so far, we have not specified the datatype of any of the parameters. As a result, PHP will try and execute the code within the function body as long as the minimum number of arguments has been received, regardless of the type of each argument. Consider the following example:

<?php
function addTwoNumbers($a, $b) {
return $a + $b;
}
echo addTwoNumbers(8, "7 apples");
// Warning: A non-numeric value encountered . . .
// 15
?>

PHP has determined that the function should add the two values received as arguments together based on the use of the addition operator (+). The first argument was a number (8), but the second argument was a string which began with a number ("7 apples"). PHP has coerced the second argument into a numeric value (7) and has carried out the addition operation, but has displayed a warning message.

What will happen if we specify that both arguments must be of type int? Let's find out:

<?php
function addTwoNumbers(int $a, int $b) {
return $a + $b;
}
echo addTwoNumbers(8, "7 apples");
// Fatal error: Uncaught TypeError: addTwoNumbers(): Argument #2 ($b) must be of type int . . .
?>

This time, PHP doesn't execute the code in the function body because we have explicitly specified that both arguments should be of type int, and the second argument is clearly a string. This is probably the behaviour you would expect, but PHP is not entirely consistent in this respect. Let's modify our code slightly and see what happens:

<?php
function addTwoNumbers(int $a, int $b) {
return $a + $b;
}
echo addTwoNumbers(8, "7");
// 15
?>

PHP has seemingly turned a blind eye to the fact that the second argument supplied is actually a string, and not an integer, and has coerced the argument to an integer anyway. No error has been generated, and no warning issued. Let's try something else:

<?php
function addTwoNumbers(int $a, int $b) {
return $a + $b;
}
echo addTwoNumbers(8, "7.5");
// Deprecated: Implicit conversion from float-string "7.5" to int loses precision . . .
// 15
?>

This time we at least get a warning because although PHP has ignored the fact that we have passed in a string to the function instead of the integer value that was expected, coercing the string "7.5" to a number produces a floating-point value rather than an integer. The code inside the function body is executed regardless, the floating-point value having been further coerced to an integer.

Apparently, the lines are somewhat blurred when it comes to deciding what kind of values a function will accept, even if we have clearly specified a datatype for each parameter. We can however force PHP to take us seriously when we specify a type for a variable by using the strict declaration in the first line of our script, as shown in the following example:

<?php
declare(strict_types=1);
function addTwoNumbers(int $a, int $b) {
return $a + $b;
}
echo addTwoNumbers(8, "7");
// Fatal error: Uncaught TypeError: addTwoNumbers(): Argument #2 ($b) must be of type int . . .
?>

Optional parameters

An optional parameter is a function parameter for which an argument does not need to be supplied in a call to the function. This is achieved by assigning a default value to the parameter in the parameter list. If a call to the function provides a value corresponding to the optional parameter, that value is used as the argument. Otherwise, the default value is used. For example:

<?php
function toPowerOf($num, $exp = 2) {
return $num ** $exp;
}
echo toPowerOf(5) . "<br>";
echo toPowerOf(5, 3);
// 25
// 125
?>

As you can see from this example, if we omit the optional second argument when calling the toPowerOf() function, the function raises the number supplied as its first argument to the power of 2 (the default exponent). When we do provide a value for the second argument, that value replaces the default exponent.

We can use any number of optional parameters in our parameter list. In fact, all of the parameters can be optional. For example:

<?php
function greetings($msg1 = "Hello World!", $msg2 = "It's a lovely day!") {
return $msg1 . " " . $msg2;
}
echo greetings() . "<br>";
echo greetings(null) . "<br>";
echo greetings("What's up?", null);
// Hello World! It's a lovely day!
// It's a lovely day!
// What's up?
?>

There are a few rules to observe when using optional parameters. First of all, optional parameters should appear after any required parameters. If this rule is not followed, the function code may or may not be executed, but a warning will be generated in any case. For example:

<?php
function greetings($msg1 = "Hello World!", $msg2) {
return $msg1 . " " . $msg2;
}
echo greetings("Good morning!", "It's a lovely day!") . "<br>";
// Deprecated: Optional parameter $msg1 declared before required parameter $msg2 is implicitly treated as a required parameter . . .
// Good morning! It's a lovely day!
?>

In this example, the second parameter ($msg2) is not assigned a default value, and is thus a required parameter for which an argument must be supplied. However, because it occupies second place in the parameter list, a value for the first argument must also be supplied in the function call, even if we specify null for that argument, in order for the required second argument to appear in the correct position in the argument list.

The second thing to note is that any optional parameters for which arguments are provided in the function call's argument list are assigned to parameters based on their position in the argument list (this constraint can be circumvented if we used named parameters, but we'll explain how that works later).

This essentially means that, if we have a number of optional parameters in our parameter list and want to skip over one or more of those parameters, we need to supply null as the argument for each skipped over parameter. For example:

<?php
function countdown($three = "Three, ", $two = "Two, ", $one = "One") {
return $three.$two.$one;
}
echo countdown() . "<br>";
echo countdown(null) . "<br>";
echo countdown(null, null);
// Three, Two, One
// Two, One
// One
?>

Optional parameters can provide us with a degree of flexibility in terms of how we call a function, but we should be careful how we use them. As a rule of thumb, having more than one or two optional parameters is generally an indication that your function is doing too much and should be split up unto two or more separate functions (the same principle applies to functions in general. Over-long parameter lists should always prompt you to question whether too much functionality is being crammed into a single function).

Named arguments

As of PHP version 8.0, we can use parameter names in function calls. This enables us to pass an argument to a function using a parameter name without having to worry about the position of the parameter in the parameter list. The parameter name, immediately followed by a colon, appears before the argument value in the function call. For example:

<?php
function toPowerOf($num, $exp) {
return $num ** $exp;
}
echo toPowerOf(exp: 3, num: 5) . "<br>";
echo toPowerOf(num: 5, exp: 3);
// 125
// 125
?>

There are a few rules to observe when using named arguments. Firstly, the parameter name supplied in the function call must be an identifier, i.e. the name of the parameter as it appears in the function definition, minus the dollar sign prefix. It cannot be a dynamic parameter name, i.e. a variable that holds the name of the parameter. For example, this works:

<?php
function add2numbers($a, $b) {
return $a + $b;
}
echo add2numbers(b: 18, a: 11) . "<br>";
// 29
?>

And this doesn't:

<?php
function add2numbers($a, $b) {
return $a + $b;
}
$name_a = "a";
$name_b = "b";
echo add2numbers($name_b: 18, $name_a: 11) . "<br>";
// Parse error: syntax error, unexpected token ":", expecting ")" . . .
?>

Named arguments can be used in a function call together with positional arguments, but the positional arguments must appear before any named arguments in the function call. Named arguments can also be used for optional parameters in any order, enabling us to override default values for specific optional values only. For any remaining optional parameters, the default values assigned to them will be used. For example:

<?php
function add5numbers($a, $b, $c=3, $d=4, $e=5) {
return $a + $b + $c + $d + $e;
}
echo add5numbers(5, 6, e: 9, c: 7) . "<br>";
// 31
?>

In the above example, we have passed the values 5 and 6 as the first two (positional) arguments in the function call to add5numbers(). These values are assigned to parameters $a and $b (the first two parameters in the function's parameter list) respectively. The next two arguments are named arguments whose values are assigned to parameters $e and $c.

The order in which we place the named arguments in the function call's argument list doesn't matter as long as they appear after any positional arguments. Note that we have not provided a value for parameter $d, so the function uses its default value (4).

One further rule governing the use of named arguments is that we cannot pass more than one argument to the same parameter. If we attempt to do so, it will generate an error. For example:

<?php
function toPowerOf($num, $exp = 2) {
return $num ** $exp;
}
echo toPowerOf(6) . "<br>";
echo toPowerOf(3, exp: 3) . "<br>";
echo toPowerOf(3, exp: 2, exp: 3);
// 36
// 27
//
// Fatal error: Uncaught Error: Named parameter $exp overwrites previous argument . . .
?>

Variable length parameter lists

It is possible to pass any number of arguments to a user defined function in PHP using a single parameter name, prefixed by the spread operator (...). A parameter that represents multiple arguments in this way is known as a variadic parameter, and the process of extracting the values represented by a variadic parameter is generally referred to as unpacking.

The arguments supplied against a variadic parameter are passed into an array, which is then unpacked using a foreach() loop. For example:

<?php
function addNumbers(...$numbers) {
$sum = 0;
foreach($numbers as $num) {
$sum += $num;
}
return $sum;
}
echo addNumbers(1, 2, 3, 4, 5, 6, 7);
// 28
?>

We can also work with a variable-length argument list using the func_get_args() function, which requires no arguments but returns the arguments supplied to the user defined function as an array. The array can then be unpacked using a foreach() loop, just as we saw in the previous example. For example:

<?php
function addNumbers() {
$numbers = func_get_args();
$sum = 0;
foreach($numbers as $num) {
$sum += $num;
}
return $sum;
}
echo addNumbers(4, 5, 6, 7);
// 22
?>

The main difference between using the spread operator and using the func_get_args() function is that the func_get_args() function retrieves all of the arguments passed to the function in which it is called. This has the advantage that we don't need to provide a parameter list for our user-defined function, but we still can pass as many arguments to it as we like.

On the other hand, the spread operator can be used together with positional arguments. The only rule here is that the variadic argument must be the last parameter in the user defined function. Consider the following example:

<?php
function addNumbers($a, $b, ...$args) {
$sum = $a + $b;
foreach($args as $num) {
$sum += $num;
}
return $sum;
}
echo addNumbers(4, 5, 6, 7, 8, 9, 10);
// 49
?>

Note that using variadic parameters precludes the use of named arguments, because the variadic parameter represents a positional argument in its own right. Being a variadic parameter it must be the last argument in the function call, but because it is also a positional argument it would have to appear before any named arguments, which means it couldn't be the last argument. For example:

<?php
function addNumbers($a, $b, ...$args, $c) {
$sum = $a + $b + $c;
foreach($args as $num) {
$sum += $num;
}
return $sum;
}
echo addNumbers(4, 5, 6, 7, 8, 9, c: 10);
// Fatal error: Only the last parameter can be variadic . . .
?>

If we try to insert a named argument before the arguments supplied against the variadic parameter, we are breaking the rule that says named arguments must appear after any positional arguments. For example:

<?php
function addNumbers($a, $b, $c, ...$args) {
$sum = $a + $b + $c;
foreach($args as $num) {
$sum += $num;
}
return $sum;
}
echo addNumbers(4, 5, c: 10, 6, 7, 8, 9, );
// Fatal error: Cannot use positional argument after named argument . . .
?>

One other thing that may have occurred to you is that if we can supply any number of arguments against a variadic parameter, it must be possible to supply arguments of different types. This is indeed the case, and could cause problems if our user-defined function is expecting all arguments to have the same type.

We can however precede the spread operator with a type declaration, which means that if one or more arguments of the wrong type are supplied as arguments, an error will be generated. For example:

<?php
function addNumbers(int ...$args) {
$sum = 0;
foreach($args as $num) {
$sum += $num;
}
return $sum;
}
echo addNumbers(4, 5, 6, 7, "Hello!");
// Fatal error: Uncaught TypeError: addNumbers(): Argument #5 must be of type int . . .
?>

We should probably point out that an error would be generated by this example even if we didn't specify that the arguments must all be integers because we are using the addition operator, which expects numeric values. Also, PHP would attempt to coerce a non-numeric argument to an integer before resorting to generating an error, so we could still do something like this:

<?php
function addNumbers(int ...$args) {
$sum = 0;
foreach($args as $num) {
$sum += $num;
}
return $sum;
}
echo addNumbers("4", "5", "6", "7", "8");
// 30
?>

In fact, as we have seen previously, the only way to strictly enforce the requirement that all arguments be of a certain type is to include the strict declaration in the first line of our script, i.e.:

declare(strict_types=1);

The return statement

As we have seen, a function can return a value to the routine that calls it, although it doesn't have to. A function that does not return a value is often referred to as a void function, and is typically used to perform some action that does not involve calculations, such as printing or updating the user interface. Assuming that we do want our function to return a value, however, we must use the return keyword.

We have already seen numerous examples of user-defined functions that return values to the calling routine using a return statement featuring the return keyword. If we write a function that carries out some form of processing but forget to add a return statement, the function will still be executed - it just won't return a meaningful value.

The return statement always begins with the return keyword, followed by the name of a variable, a constant value, or an expression. The value returned can be of any type, including an array or an object. The return statement terminates the execution of the function immediately, and returns control to the calling routine, even if the function does not return a value.

A void function always returns null. The return statement in a void function, if used, simply consists of the return keyword followed by a semicolon . For example:

<?php
function greeting() {
echo "Hello World!";
return;
}
greeting();
// Hello World!
?>

Note that the return statement in the above example is entirely superfluous. We could leave it out altogether and the result would be exactly the same. There may be situations, however, in which we want to break out of a function before all of the code in the function body has been executed. This typically occurs in void functions where the execution of a procedure depends on some condition being met. For example:

<?php
function greeting($user) {
if ($user == "") {
echo "There are no users.";
return;
}
echo "Hello $user!";
}
$user = "";
.
.
.
greeting($user);
// There are no users.
?>

The return statement exits the function immediately. Any code that follows the return statement in a function is not executed. In the above example, if we had not included the return statement, function execution would have continued, which is not what we want to happen. For example:

<?php
function greeting($user) {
if ($user == "") {
echo "There are no users.";
}
else {
echo "Hello $user!";
}
$user = "";
.
.
.
greeting($user);
// There are no users.
?>

As with function parameters, we can specify a return type for the return value by adding a colon (:), followed by the required datatype, immediately after the parenthesised parameter list and before the opening curly brace of the function body. For example, we might want to carry out a calculation involving numeric variables, but return the result as a string, as shown here:

<?php
function quotient($a, $b): string {
return $a / $b;
}
$result = quotient(15, 4);
var_dump($result)
// string(4) "3.75"
?>

If we were to omit the return type, the type returned by the quotient() function would depend on the values passed to it. For example:

<?php
function quotient($a, $b) {
return $a / $b;
}
$result = quotient(15, 4);
var_dump($result);
echo "<br>";
$result = quotient(16, 4);
var_dump($result);
// float(3.75)
// int(4)
?>

As we have mentioned, a function may only return a single value, but that return value can be of any type, including an array. We can therefore return multiple values - we just have to put them into an array within the function body. For example, suppose we had a price list and wanted to increase all of the prices by some fixed percentage. We could do something like this:

<?php
function priceIncrease($list, $percent) {
$newPriceList = [];
foreach($list as $item=>$price) {
$price += ($price * $percent)/100;
$price = round($price, 2);
$newPriceList[$item] = $price;
}
return $newPriceList;
}

$priceList = [
"Apples" => 0.50,
"Pears" => 0.65,
"Oranges" => 0.75
];

$newPriceList = priceIncrease($priceList, 10);
echo "New price list: <br><br>";
foreach($newPriceList as $item=>$price) {
echo "$item: $price<br>";
}
// New price list:
//
// Apples: 0.55
// Pears: 0.72
// Oranges: 0.83
?>

Passing by reference

Arguments are usually passed to a function by value, which means that if we supply the name of a variable to a function as an argument, the function only receives a copy of the variable which effectively becomes a local variable. Changes made to the local variable do not affect the value of the original. For example:

<?php
function double($num) {
$num *= 2;
return $num;
}

$var = 4;
$newVar = double($var);
echo "$newVar<br>";
echo $var;
// 8
// 4
?>

Sometimes, we want a function to change the value of a variable that is passed to it. We can achieve this by passing the variable to our function by reference. This means that, rather than passing a copy of the variable to the function, we are actually passing the address of that variable in memory, allowing the function to access it directly. We do this by prefixing the variable's name in the function's parameter list with an ampersand (&), otherwise known as the address of operator. For example:

<?php
function double(&$num) {
$num *= 2;
}

$var = 5;
echo $var . "<br>";
double($var);
echo $var;
// 5
// 10
?>

This only works for variables, however. If you try passing a constant value to a function for a parameter that is passed by reference, an error will be generated. For example:

<?php
function double(&$num) {
$num *= 2;
}

const MAX_SIZE = 100;
echo MAX_SIZE . "<br>";
double(MAX_SIZE);
echo MAX_SIZE;
// 100
// Fatal error: Uncaught Error: double(): Argument #1 ($num) cannot be passed by reference . . .
?>

Note that non-scalar variables, i.e. variables that are objects, are not passed to functions by value. Nor, strictly speaking, are they passed by reference. When you pass the variable name of an object to a function, what you are actually supplying is a copy of the object identifier, otherwise known as a handle or a pointer. This is often a source of confusion, even among experienced developers.

The important thing to note here is that the pointer passed to the function, despite only being a copy of the object identifier stored in the original variable, points to the same location in memory, i.e. the memory location where the object itself is stored. Changes to the object's properties within the function are thus changes to the object itself, but changes to the pointer will not affect the original variable.

Putting this another way, we can't make the original variable point to a different memory location, such as the address of a new object. If we were to prefix the parameter name in the function definition with the address of operator, it would be a different story, because then we would be passing the variable by reference - but we're not going to go there.

Hopefully, the above explanation will clear up some of the confusion about what's actually happening when object variables are passed to functions. Meanwhile, let's suppose we want to create a function that updates one or more object properties. We supply the object's variable name to the function, just like any other type of variable. As a result, the function receives a pointer to the address in memory where the object is located. Here's an example:

<?php
$person = new stdClass();
$person->name = "Chris Wells";

function updateEmail($person, $email) {
$person->email = $email;
}

print_r($person);
echo "<br>";
updateEmail($person, "webmaster@technologyuk.net");
print_r($person);
// stdClass Object ( [name] => Chris Wells )
// stdClass Object ( [name] => Chris Wells [email] => webmaster@technologyuk.net )
?>

We have created the object variable $cwells using the stdClass() function, which allows us to create an object that is an instance of a generic empty class and define its properties dynamically, without having to formally define a new class. We then define a single property (name) and assign it the value "Chris Wells".

The updateEmail() function has two parameters, $person and $email, and assigns the value supplied for the $email parameter to the email property of $person. This will only work, of course, it the argument suppled for the $person parameter is an object. It doesn't matter that the object in question doesn't have a property called email, because the property will be created dynamically.

In this case, as you can see, the function has successfully modified the $cwells object by adding the email property and assigning it a value.

Function variables

As we have previously stated, the variables or constant values passed in to a function as arguments essentially become local variables, and will be used as such by the code in the body of the function. We can however declare and use additional variables within a function if required.

These function variables have local scope, sometimes referred to as block scope, or more specifically as function scope. That means they are not visible outside of the function in which they are declared. It also means that we can re-use a variable name we have used in the global scope for a variable inside the function without creating a conflict. For example:

<?php
function outputMsg($message) {
$msg = "Message: $message";
echo $msg;
}

$msg = "Hello World!";
outputMsg("Greetings and welcome to Earth!");
echo "<br>";
echo $msg;
// Message: Greetings and welcome to Earth!
// Hello World!
?>

We can re-use global variable names because variables declared in the global scope are not visible inside a function by default. If we must access a global variable inside a function, we can do so using the global keyword in front of the variable name inside the function. For example:

<?php
$count = 100;

function incrementCount() {
global $count;
$count += 1;
}

echo $count . "<br>";
incrementCount();
echo $count;
// 100
// 101
?>

Accessing global variables from inside a function in this way is generally considered poor practice and should be avoided if at all possible. There is almost always a better way of doing things. For example, we could rewrite the previous example as follows:

<?php
$count = 100;

function incrementCount($count) {
$count += 1;
return $count;
}

echo $count . "<br>";
$count = incrementCount($count);
echo $count;
// 100
// 101
?>

These examples are of course extremely trivial. If we really want to increase the value of a variable by one, we can do so in one line of code using the assignment by addition operator. The examples merely serve to make a point, namely that you shouldn't need to access a global variable inside a function. Doing so, especially in a large codebase involving a number of developers, can make it much harder to track down bugs.

Nested functions

A function can be defined inside another function's code block, but the inner (nested) function cannot be called until the outer function has been called, because up until that point it does not exist as far as any code outside the outer function is concerned. For example:

<?php
function question($query) {
echo $query;
function reply($answer) {
echo $answer;
}
}
reply("What was the question?");
// Fatal error: Uncaught Error: Call to undefined function reply() . . .
?>

Let's try that again, but this time we'll call the outer function first:

<?php
function ask($question) {
echo $question;
function reply($answer) {
echo $answer;
}
}
ask("Where are my glasses?");
echo "<br>";
reply("You're wearing them!");
// Where are my glasses?
// You're wearing them!
?>

We can also call the nested function from within the outer function:

<?php
function ask($question) {
echo $question;
function reply($answer) {
echo $answer;
}
echo "<br>";
reply("You're wearing them!");
}
ask("Where are my glasses?");
// Where are my glasses?
// You're wearing them!
?>

Nested functions, like all functions, become available in the global scope once they are defined, which only happens when the outer function has been called for the first time. The drawback with this is that the inner function is redefined each time the outer function is called, even though this is not necessary once the outer function has been called, which impacts negatively on performance.

Generally speaking, there are usually better alternatives to creating nested functions. Unless you have a compelling reason to do so, we would suggest that you avoid using them.

Recursive functions

A recursive function is a function that calls itself repeatedly until some exit condition is satisfied. Recursive functions are useful when we want to carry out the same action repeatedly. We might use a recursive function, for example, to process the items in an array that represents a list or a queue. The following example demonstrates how we might use a recursive function to find the factorial of a number:

<?php
function factorial($n) {
if ($n <= 1) {
return 1;
}
return ($n * factorial($n - 1));
}
echo factorial(5) . "<br>";
echo factorial(10) . "<br>";
echo factorial(15);
// 120
// 3628800
// 1307674368000
?>

The factorial of a positive integer is the product of all positive integers that are less than or equal to that integer. The factorial of 5 - written as 5! - is calculated as 1 × 2 × 3 × 4 × 5 = 120. In the above example, we have defined a recursive function that calculates the factorial of any positive integer up to about 170 on a 64-bit system - after that, the result is a number too large for PHP to display and it simply returns infinity.

For the final invocation of the factorial() function in our example, the function calls itself recursively 14 times before returning the factorial of 15. We need to be mindful that each time a recursive function calls itself, memory has to be allocated for the function call on the stack. This is time consuming, and can eat up a lot of memory, which is only released when the last call to the recursive function returns.

Recursive functions can be powerful tools for carrying out repetitive tasks, but they should always be used with caution, and only when there is no suitable alternative. The most important thing to remember is that there must be an exit condition that is guaranteed to be satisfied at some point in the recursion cycle. A recursive function that calls itself endlessly will eventually run out of stack space and cause your script to crash.

Functions as variables

In PHP, we can assign a function to a variable. If the variable name subsequently appears in a script with parentheses appended to it, PHP searches the script for a function with that name and, if found, attempts to execute the function. For example:

<?php
function helloWorld() {
print "Hello World!";
}
$greeting = "helloWorld";
.
.
.
$greeting();
// Hello World!
?>

The function in question may be either a named function (as per the above example) or an anonymous function (we'll be looking at anonymous functions in more detail shortly). In the following example, we use an anonymous function:

<?php
$greeting = function() {
print "Hello World!";
};
.
.
.
$greeting();
// Hello World!
?>

Note that, in the above example, we have assigned the function directly to the $greeting variable. The function itself does not have a name. Note also that the function definition is followed by a semi-colon. That's because the function definition forms part of a program statement.

Like any other function, a variable function can accept arguments, as demonstrated by the following example:

<?php
function toPowerOf($num, $exp = 2) {
echo $num ** $exp;
}

$exp = "toPowerOf";
.
.
.
$exp(5, 3);
// 125
?>

We can achieve the same thing using an anonymous function:

<?php
$exp = function ($num, $exp = 2) {
echo $num ** $exp;
};
.
.
.
$exp(5, 3);
// 125
?>

Assigning a function to a variable in this way provides a mechanism whereby we can pass a function to another function as an argument. A function that is passed as an argument to another function is known as a callback function, and will be called from within the function that accepts it as an argument.

Callback functions

As we have mentioned, a callback function is a function that is passed to another function as an argument, and is executed during the execution of that function, either at a specific point during execution or when some precondition is satisfied. The callback function argument can be either the name of a function or a variable to which an anonymous function has been assigned (more about anonymous functions later). It can even be defined as an anonymous function inline. Let's look at a simple example:

<?php
function calculate($num, $func) {
return $func($num);
}

function square($num) {
return $num * $num;
}

$result = calculate(5, "square");
echo $result;
// 25
?>

In the above example, the calculate() function receives two arguments. The first argument is a numeric value (5), and the second argument is the name of the callback function ("square"). The calculate() function invokes the callback function with the numeric value 5 as its argument and returns the result, which is assigned to the variable $result.

At this point you are probably thinking that we could have achieved the same result just using the square() function, which is a reasonable observation. However, using a callback function means that we can get the calculate() function to do different things, depending on the callback function provided. For example:

<?php
function calculate($num, $func) {
return $func($num);
}

function square($num) {
return $num * $num;
}

function cube($num) {
return $num * $num * $num;
}

function factorial($num) {
if ($num <= 1) {
return 1;
}
return ($num * factorial($num - 1));
}

$result = calculate(6, "square");
echo "$result<br>";
$result = calculate(6, "cube");
echo "$result<br>";
$result = calculate(6, "factorial");
echo $result;
// 36
// 216
// 720
?>

In the above examples, each callback function has been passed to the calculate() function as a string specifying the name of a function. We could also supply a variable to which an anonymous function has been assigned as a callback function argument. For example:

<?php
function calculate($num, $func) {
return $func($num);
}

$square = function($num) {
return $num * $num;
};

$cube = function($num) {
return $num * $num * $num;
};

$factorial = function($num) use (&$factorial) {
if ($num <= 1) {
return 1;
}
return ($num * $factorial($num - 1));
};

$result = calculate(6, $square);
echo "$result<br>";
$result = calculate(6, $cube);
echo "$result<br>";
$result = calculate(6, $factorial);
echo $result;
// 36
// 216
// 720
?>

You might have noticed that, in the above example, the function definition for the anonymous function assigned to $factorial includes the directive use (&$factorial) after the parameter list and before the function body. That's because the function needs to call itself from within a closure.

A closure is an instance of the Closure class. All you really need to know about this class for the moment is that anonymous functions are automatically instantiated as objects of this type, and that those objects exist in their own (closed) namespace.

The closure will not have access to variables that are external to its namespace, which in this case includes the $factorial variable. The use language construct allows you to access external variables from inside the closure, enabling the recursive anonymous function to call itself.

A number of PHP's built-in functions - for example array_map() and usort() - accept other functions, either built-in or user-defined, as callback function arguments. For example:

<?php
$myArray = ["Jan", "Feb", "Mar"];

function upperCase($str) {
return strtoupper($str);
}

$new_array = array_map("strtoupper", $myArray);
print_r($new_array);
echo "<br>";
$another_array = array_map("upperCase", $myArray);
print_r($another_array);
// Array ( [0] => JAN [1] => FEB [2] => MAR )
// Array ( [0] => JAN [1] => FEB [2] => MAR )
?>

In the above example we first pass the built-in strtupper() function to the array_map() function as a callback function argument and return the result to the $new_array variable. Next, we pass it the user-defined upperCase() function (which as you can see is just a wrapper for the strtoupper() function), and assign the result to the $another_array variable. The result is the same in both cases.

PHP supports a number of callback types, including function names, anonymous functions, class methods and callable objects (we'll talk more classes and objects elsewhere). Hopefully you can see how, using callback functions, we can write generic functions that will produce different results depending on the callback function provided as an argument.

Anonymous functions

Anonymous functions, sometimes referred to as closures or lambda functions, are functions that do not have a name. As we have stated previously, they are typically used as callback functions, i.e. functions that are passed as callable arguments to other functions, but they have other uses as well.

As we have also previously mentioned, anonymous functions are automatically implemented as instances of PHP's internal Closure class, a complete definition of which can be found on the PHP Group website.

A closure encapsulates its scope, and has no access to variables that exist in the parent scope, i.e. the scope within which it is defined. For example:

<?php
$message = "Hello World!";

$greeting = function() {
echo $message;
};

$greeting();

// Warning: Undefined variable $message
?>

At this point you may be thinking, OK so what? Named functions don't have access to external variables either, unless we use the global keyword in front of an external variable within the function (not a particularly good thing to do) or pass the variable to the function as one of its arguments (which is preferable).

If we assign an anonymous function to a variable and then call the anonymous function directly using the variable name, we can pass external variables to the function. For example, we could rewrite the previous example as follows:

<?php
$message = "Hello World!";

$greeting = function($msg) {
echo $msg;
};

$greeting($message);

// Hello World!
?>

In these examples, both the named function and the anonymous function are defined outside of any other function, which means it is possible to pass arguments to them in the usual way. The same is true if we pass an anonymous function to another function as a variable. We saw how this works when we looked at callback functions. The following example is similar to one we saw earlier, but uses an anonymous function assigned to a variable instead of a named function:

<?php
function calculate($num, $func) {
return $func($num);
}

$square = function($num) {
return $num * $num;
};

$result = calculate(5, $square);
echo $result;
// 25
?>

Declaring an anonymous function outside of any other function means we can re-use that function as many times as we like in our script. But if that's how we're going to use the function we might as well make it a named function instead of assigning it to a variable as an anonymous function.

Sometimes we just need a function that's only going to be used once, and this is where anonymous functions come into their own, particularly when it comes to callback functions. As we know, a callback function is a function that is passed to another function as a parameter. The callback function can then be invoked inside the host function.

If we want to pass a function to another function as an argument, and if this is going to be a one-time occurrence, it might make sense to define the function argument as an anonymous function inline, i.e. as part of the function call. For example, we could do something like this:

<?php
function calculate(int|float $n, closure $func) {
echo "The result of the calculation is: ";
return $func();
}

$result = calculate($n = 6, function() use ($n) {return $n ** 2;});
echo "$result<br>";
$result = calculate($n = 7, function() use ($n) {return $n * 4;});
echo "$result";
// The result of the calculation is: 36
// The result of the calculation is: 28
?>

The calculate() function takes two arguments, the first of which can be either an integer or a floating-point value (or a value that can be coerced to one of those types). The second argument must be a closure (i.e. an anonymous function). We make two calls to calculate(), each with a different value for $n, and each with a different inline anonymous function definition.

As a closure, the anonymous function cannot see the variable $n because it cannot see any variables outside its own closed scope. We overcome this limitation with the use language construct, which allows a closure to inherit one or more variables from the parent scope (the parent scope of a closure is the function in which the closure is defined).

This is probably not the best example, because the first argument to calculate() can't simply be a constant value - we have to provide a variable name so that the use language construct can be used to make the variable available to the anonymous function (this limitation does not apply when we use arrow functions, as we will see).

We can supply an anonymous function as a callback function to several of PHP's built-in functions. For example, suppose that instead of applying a function to a single numeric variable, we wanted to carry out the same type of calculation for multiple variables stored in an array. We could do something like this:

<?php
$numArray = [2, 3, 4, 5, 6];

$cubeArray = array_map(function($num) {
echo "$num^3 = " . $num ** 3 . "<br>";
return ($num ** 3);
}, $numArray);

echo "<br>";
var_dump($cubeArray);

// 2^3 = 8
// 3^3 = 27
// 4^3 = 64
// 5^3 = 125
// 6^3 = 216
//
// array(5) { [0]=> int(8) [1]=> int(27) [2]=> int(64) [3]=> int(125) [4]=> int(216) }
?>

The array_map() function takes a callback function as its first argument. The remaining (one or more) arguments are all arrays. The callback function is applied to each element of the arrays passed to it and the results used to build a new array, which is returned by the array_map() function.

The callback function can be either a named (built-in or user-defined) function, or an anonymous function. In the above example, we have used an inline anonymous function in the function call that returns the cube of each element in the array.

Arrow functions

Arrow functions, which first appeared with PHP version 7.4, provide a more compact syntax for writing anonymous functions. One major difference between arrow functions and anonymous functions is that we don't have to explicitly give an arrow function access to variables in the parent scope with the use language construct, because access to those variables is automatically granted.

To demonstrate how arrow functions work, we can rewrite one of the examples we saw earlier as follows:

<?php
function calculate(int|float $n, closure $func) {
echo "The result of the calculation is: ";
return $func($n);
}

$result = calculate(6, fn($n) => $n ** 2);
echo "$result<br>";
$result = calculate(7, fn($n) => $n * 4);
echo "$result";
// The result of the calculation is: 36
// The result of the calculation is: 28
?>

Arrow functions have the following basic syntax:

fn(argument_list) => expression

For arrow functions, as you may have noticed, the keyword function is replaced by fn, and the return keyword disappears altogether. An arrow function contains one, and only one, expression. Whatever that expression evaluates to is the arrow function's return value. Arrow functions thus make it possible to write basic anonymous functions in a significantly more compact format, although arguably at the expense of readability.