Functions in PHP

In the previous article, we showed you how to get PHP up and running and presented some basic elements. In this section, we’d like to update your knowledge of the functions – how to create them, call them and process their output. We’ll describe classic functions, but also anonymous and the so-called arrow functions.

Function is the basic building block of a program. It’s a “subprogram” that allows us to execute a sequence of commands. As a rule, a function receives several input arguments (or none) at the input and can return some value or just execute commands. Functions help us reuse the same code in multiple places (we can also call it the DRY principle – Don’t repeat yourself). This way you can clarify your program and divide it into smaller functional parts. We have built-in and user-defined functions.

A function consists of several parts, namely:

  • title (name) – name of the function that should describe its role – short and concise (see PHP: Rules – Manual)
  • input arguments – a list of input arguments that are necessary for the proper running of the function
  • function body – a sequence of commands that perform the required task
  • return value – the function can return an output value using the return command
  • function description – description of the function, its parameters, return value

We can call the function only if it’s defined (created) before the call, i.e. our program already knows it. Use the keyword “function” to create a function. 

An example of a function that does nothing. It has a name, no input arguments, an empty body, it doesn’t return anything (no return value):

<?php
function nothing()
{}

or we can try something meaningful, e.g.:

<?php
function content_of_circle_using_radius($radius)
{ 
    return M_PI * $radius ** 2;
}

This function has a name, an input argument, a body and a return value. The function defined in this way (as the name implies) returns the area of the circle according to the specified argument (it should be a radius). We can reasonably deduce that from the definition.

However, there may be situations created in the program when the function doesn’t return the expected result. E.g. if $radius = ‘xXx’; at best it throws out an exception (from php v8.0) or returns a 0. What about the return value? In this case, it’s still a float, but if we don’t know (see) the body of the function, it could be anything. Therefore, we shouldn’t rely on float, but we should also treat it after calling the function so that the program behaves as expected.

This can be avoided a little and the definition of the function can be supplemented by the types of input arguments and the type of return value – this can be described in the function description. In order for the interpreter to verify and evaluate it for us, we turn on the so-called strict mode. Insert declare(strict_types=1) at the beginning of the file. In strict mode, PHP expects values with the same type as the target types. If a mismatch occurs, PHP invokes an exception (TypeError). Like this: 

<?php
declare(strict_types=1);
/**
* @param float $radius radius, should be positive value
*
* @return float
*/
function content_of_circle_using_radius(float $radius): float
{
   return M_PI * $r ** 2;
}

Now we know by definition that the argument must be a float and the return value will also be a float. We can certainly rely on that now.

When creating functions, it’s sometimes advisable to set the default value of the argument. This is used mainly if we assume that the argument will be the same in most cases. To define the default value, we simply assign it to a variable.

<?php
declare(strict_types=1);
function how_you_feel(string $name, int $age = 25): string
{
   return $name . " feels " . $age . " years old";
}
how_you_feel("Tomas"); // returns Tomas feels 25 years old
how_you_feel("Viktor", 43); // returns Viktor feels 43 years old

This actually tells us that the argument is optional and we can omit it when calling the function. Here, however, it should be noted that optional arguments, or arguments with default values, should be placed after the mandatory arguments. Since PHP v8.0.0, it’s deprecated to declare optional arguments before mandatory ones. However, there’s an exception – if an optional argument has a specified type and has a declared default value of null, then it’s possible to place it before the mandatory argument. So it’s essentially necessary to enter it when calling, but we can use a null value.

<?php
declare(strict_types=1);
function foo($bar, $baz) {} // all ok
function foo($bar, $baz = null) {} // all ok
function foo($bar = null, $baz){} // deprecated as of PHP v8.0.0
function foo(int $bar = null, $baz){} // all ok

When declaring a function, we can also use “…”, the so-called spread operator. This notation tells us that a function can have a variable number of arguments.

<?php
declare(strict_types=1);
function foo($bar, ...$baz) {}
foo(1, 2); // valid call
foo(1, 2, 3); // valid call

The $baz argument is a field of arguments, i.e. in the function body, the argument can be scrolled as a classic field, e.g. by foreach. We can even set the type for the given variable arguments, 

<?php
declare(strict_types=1);
function foo($bar, int ...$baz) {}
foo(1, 2); // valid call
foo(1, 2, 3); // valid call
foo(1, 2, "3"); // raise TypeError

“…” can be used not only in defining the function, but also in calling.

<?php
declare(strict_types=1);
function foo($bar, $baz) {}
$args = [1, 2];
foo(...$args); // $bar = 1, $baz = 2

Until now, all function definitions had to strictly follow the order of arguments when calling. If, for example, we have a function with three optional parameters and we want to only change the last one, we have to set the other ones too.

But that changed with the arrival of PHP v8.0. Named arguments were introduced as an extension of existing position parameters. Named arguments allow you to pass arguments to a function based on the parameter name and not based on the position of the parameter. This makes the arguments independent of the order and allows you to arbitrarily skip the default values.

<?php
declare(strict_types=1);
function foo(
    int $bar = null,
    int $baz = 1,
    string $qux = '',
    string $quux = '',
) {}
foo(qux: 'some string'); // named argument
// $bar = null, $baz = 1, qux = 'some string', $quux = ''
foo(null, 1, 'some string'); // old style
// $bar = null, $baz = 1, qux = 'some string', $quux = ''

Since version PHP v8.1 we can also use “…” (spread operator). The order of arguments in an array doesn’t matter.

<?php
declare(strict_types=1);
$args = [
    'baz' => 2,
    'bar' => 4
];
foo(...$args, quux: 'some string');
// $bar = 4, $baz = 2, qux = '', $quux = 'some string'

We talked about how to pass arguments to a function. It was with a value, a default value, a spread operator and named arguments. The last option is passing arguments using a reference. This means that if we change the argument in the body of the function, the argument outside the body of the function itself changes in the parent scope. Just add the ampersand (&) character before the argument in the function definition.

By default, PHP passes arguments by value, but if the argument is an instance of an object, it is passed by reference.

<?php
declare(strict_types=1);
$a = 1
function modify(int &$a): int { return $a++; }
$result = modify($a);
echo $result; // 1
echo $a;      // 2

In the introduction, we stated that a function consists of several parts. One of them is the name. The function must have a name if we want to call it. Reasonable, but there are also so-called anonymous functions. They have everything that was mentioned, but they don’t have a name. They’re known as closures. They’re mainly used as callable arguments in a function. But they also have other uses as they are an implementation of the Closure class.

We can use them as a callable argument, for example, in the usort function. This will sort the input field by return value from the anonymous function.

<?php
declare(strict_types=1);
$arr = [
    'baz' => 2,
    'bar' => 4,
    'qux' => 1
];
usort($arr, function($a, $b) {
    if ($a === $b) return 0;
    return ($a < $b) ? -1 : 1;
});

Anonymous functions have their own local scope, so it isn’t possible to use a variable outside the body of the function. If we still need to access a variable defined outside the body of the function, we use the use operator.

<?php
declare(strict_types=1)
$message = "I am compare %s and %s".PHP_EOL;
$anonym = function($a, $b) use ($message) {
    echo sprintf($message, $a, $b);
    if ($a === $b) return 0;
    return ($a < $b) ? -1 : 1;
};
$arr = [4, 6, 8, 0];
usort($arr, $anonym);

If we needed a simple function, for example, to multiply each element of a field by a constant, we could use the so-called arrow function. It’s defined simply by fn(arguments) => expr. It has the same functionality as the anonymous function, but we can use the variables from the parent scope. 

<?php
declare(strict_types=1);
$arr = [4, 6, 8, 0];
$multiplier = 5;
$result = array_map(fn($x) => $x * $multiplier, $arr);
// same as 
$result = array_map(
              function($x) use ($multiplier) { 
                  return $x * $multiplier;
              },
              $arr
          );

The next blog in our PHP series will be dedicated to generators. Until then, good luck coding ?