DEV Community

Cover image for In PHP, function parameters
Maico Orazio
Maico Orazio

Posted on • Updated on

In PHP, function parameters

In this article, we will discuss function arguments, default values, type unions, argument unpacking, pass by value or reference, and finally, introduced in PHP 8, named arguments.

To define function parameters

We can define function parameters within the parentheses of the function definition.

function foo($x, $y) 
{
    return $x * $y;
}
Enter fullscreen mode Exit fullscreen mode

When we call the foo() function, we need to pass two arguments.

echo foo(5, 10); // 50
Enter fullscreen mode Exit fullscreen mode

Parameters are the variables listed in the function definition, while arguments are the actual values we pass to the function when we invoke it; in the example above, $x and $y are the function's parameters, and 5 and 10 are the passed arguments, i.e., the values assigned to the parameters of the foo() function.

Type Hinting

Type Hinting is a feature introduced by PHP starting from version 5, which allows specifying the type of object/data passed as an argument to a function or method.

Starting from PHP version 7, in addition to the weak mode, there is also the strict mode. By enabling this mode, we force PHP to interpret variable types, and if the type is not satisfied, we will receive a fatal error.

So, if in the example above, we want to be more restrictive and accept only the int type and no others, we should declare it at the top of the code using the declare statement followed by strict_types=1.

declare(strict_types=1);

function foo(int $x, int $y) 
{
    return $x * $y;
}
Enter fullscreen mode Exit fullscreen mode

Now, if we try to pass an argument of type float or string, we will receive an error.

echo foo(5.0, '10');
// Fatal error: Uncaught TypeError: foo(): Argument #1 must be of type int, float given, called in ...
Enter fullscreen mode Exit fullscreen mode

Union of types

Starting from PHP version 8, it is possible to accept multiple data types. We can specify type unions using the | character.

declare(strict_types=1);

function foo(int|float $x, int|float $y) 
{
    return $x * $y;
}
echo foo(5.0, 10); // 50
Enter fullscreen mode Exit fullscreen mode

In this case, we can invoke the foo() function by passing both integers and decimals as arguments.

Default values

For every parameter listed in the function definition, it is necessary to pass an argument; otherwise, we will receive a fatal error.

echo foo(5.0); 
// Fatal error: Uncaught ArgumentCountError: Too few arguments to function foo(), 1 passed in ...
Enter fullscreen mode Exit fullscreen mode

It is possible to set default values for parameters to assign a value in case it is not passed as an argument.

declare(strict_types=1);

function foo(int|float $x, int|float $y = 10) 
{
    return $x * $y;
}
echo foo(5.0); // 50
Enter fullscreen mode Exit fullscreen mode

You can assign scalar arrays and null values as default values, but not a function call or an object; it must be a constant expression.

Another important thing is that optional parameters must be defined after any required parameter.

Pass arguments by value vs by reference

As a default, arguments for required parameters are passed by value. You can specify to pass an argument by reference using the & character in the parameter definition.

For example, let's define a foo() function that takes 2 arguments and returns the multiplication of the arguments, dividing the first argument by 2 if it is an even number.

declare(strict_types=1);

function foo(int|float $x, int|float $y): int|float 
{
    if ($x % 2 === 0) {
        $x /= 2;
    }
    return $x * $y;
}

$a = 6.0;
$b = 7;
echo foo($a, $b); // 21
var_dump($a, $b); // float(6) int(7)
Enter fullscreen mode Exit fullscreen mode

We have mentioned that by default, arguments are passed by value. In fact, modifying the value of $x ($x /= 2;), which is the first argument passed to the function, does not affect the original variable $a, which still holds the value float(6).

If we change the definition of the first argument to pass the reference to the variable $a, in this case, modifying the parameter $x also affects the value of the original variable $a.

declare(strict_types=1);

function foo(int|float &$x, int|float $y): int|float 
{
    if ($x % 2 === 2) {
        $x /= 2;
    }
    return $x * $y;
}

$a = 6.0
$b = 7;
echo foo($a, $b); // 21
var_dump($a, $b); // float(3) int(7)
Enter fullscreen mode Exit fullscreen mode

Decompression of topics

We want to define a function that returns the sum of 2 or more arguments. We can change the function definition as we add an argument, or we can use the spread operator that will capture any arguments passed to the function in an array.

declare(strict_types=1);

function sum(...$numbers): int|float 
{
    $sum = 0;
    foreach ($numbers as $number) {
        $sum += $number;
    }
    return $sum;
    //return array_sum($numbers);
}

$a = 6.0
$b = 7;
echo sum($a, $b, 10, 25); // 48
Enter fullscreen mode Exit fullscreen mode

You can use the spread operator even after a fixed number of parameters, in which case only the arguments passed after the fixed ones will be added to the array.

declare(strict_types=1);

function sum(int|float &$x, int|float $y, ...$numbers): int|float 
{
    return $x + $y + array_sum($numbers);
}

$a = 6.0
$b = 7;
echo sum($a, $b, 10, 25); // 48
Enter fullscreen mode Exit fullscreen mode

In the example above, $x and $y are equal to 6.0 and 7, respectively, and all the remaining arguments passed to the function are captured within the $numbers array.

We can specify the type of the extra arguments passed to the function.

declare(strict_types=1);

function sum(int|float &$x, int|float $y, int|float...$numbers): int|float 
{
    return $x + $y + array_sum($numbers);
}

$a = 6.0
$b = 7;
echo sum($a, $b, 10, 25, '8');
// Fatal error: Uncaught TypeError: sum(): Argument #5 must be of type int|float, string given, called in ...
Enter fullscreen mode Exit fullscreen mode

In this case, we receive an error because the last argument passed to the function is a string.

The spread operator can also be used to unpack an array into the list of arguments to be passed to a function.

declare(strict_types=1);

function sum(int|float &$x, int|float $y, int|float...$numbers): int|float 
{
    return $x + $y + array_sum($numbers);
}

$a = 6.0
$b = 7;
$numbers = [10, 25, 8];
echo sum($a, $b, ...$numbers); // 56
Enter fullscreen mode Exit fullscreen mode

 named arguments

In PHP 8.0, we have the ability to specify the name of the argument to be passed to the function, regardless of the order listed in the function definition.

Let's define a function where, if the first parameter $x is divisible by the second parameter $y, it returns their division; otherwise, it returns the value of $x.

declare(strict_types=1);

function foo(int &$x, int $y): int 
{
    if ($x % $y === 0) {
        return $x / $y;
    }
    return $x;
}

$a = 6
$b = 3;
echo foo($a, $b); // 2
Enter fullscreen mode Exit fullscreen mode

What happens if we change the order of the arguments passed to the function?

echo foo($b, $a); // 3
Enter fullscreen mode Exit fullscreen mode

We get the value of $b because it's no longer divisible. This new feature in PHP 8 effectively allows us to name the argument to indicate the value of the respective parameter declared in the function, regardless of the order.

echo foo(y: $b, x: $a); // 2
Enter fullscreen mode Exit fullscreen mode

This feature proves to be very useful in various use cases. For example, if we want to change the order of parameter definitions, we would have to search throughout the code where the function is used and update the order of how we pass arguments. With named arguments, we no longer need to worry about updating existing functions because the order is not important. However, if we change the name of a parameter, we still need to update the name in every part of the code where it is used.

Another very valid use case is when you have a series of parameters with default values. For example, PHP has a function called setcookie() that has many parameters with default values. If we wanted to pass only the name and the last argument, we would still have to pass all the others defined before the last one.

/*
method signature
setcookie (
 string $name,
 string $value = "",
 int $expires = 0,
 string $path = "",
 string $domain = "",
 bool $secure = false,
 bool $httponly = false,
) : bool
*/

setcookie('test', '', 0, '', '', false, true);
// named arguments
setcookie(name: 'test', httponly: true);
Enter fullscreen mode Exit fullscreen mode

Passing the same argument multiple times results in an error because it overwrites the previous argument.

echo foo(x: $b, x: $a);
// Fatal error: Uncaught Error: Named parameter $x overwrites previous argoument in ...
Enter fullscreen mode Exit fullscreen mode

You can combine named arguments with positional arguments as long as the named arguments come after the positional ones.

echo foo($a, y: $b); // 2
Enter fullscreen mode Exit fullscreen mode

Passing x again conflicts because we already have an argument for the $x parameter.

echo foo($a, x: $b);
// Fatal error: Uncaught Error: Named parameter $x overwrites previous argoument in ...
Enter fullscreen mode Exit fullscreen mode

Another thing to note is that if we use argument unpacking and the array contains keys, those keys will be treated as argument names.

$arr = ['y': 3, 'x' => 6];
echo foo(...$arr); // 2
Enter fullscreen mode Exit fullscreen mode

Good work 👨‍💻

Top comments (2)

Collapse
 
moopet profile image
Ben Sinclair

A couple of tiny copy-paste issues with your demo code:

if ($x % 2 === 2) will never be true, since $x % 2 can only be an int in the range [0, 1].

You're missing a semicolon after $a = 6.0

Collapse
 
mainick profile image
Maico Orazio

@moopet thank you for the report; I have made the necessary changes to the code