In PHP, function parameters
Tags: php8, php, functional
Published: 2023-10-06 15:30:00

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. 

```php
function foo($x, $y) 
{
    return $x * $y;
}
```

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

```php
echo foo(5, 10); // 50
```

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`.

```php
declare(strict_types=1);

function foo(int $x, int $y) 
{
    return $x * $y;
}
```

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

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

## Union of types

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

```php
declare(strict_types=1);

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

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.

```php
echo foo(5.0); 
// Fatal error: Uncaught ArgumentCountError: Too few arguments to function foo(), 1 passed in ...
```

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

```php
declare(strict_types=1);

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

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.

```php
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)
```

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`.

```php
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)
```

## 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.

```php
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
```

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.

```php
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
```

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.

```php
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 ...
```

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.

```php
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
```

## 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`.

```php
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
```

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

```php
echo foo($b, $a); // 3
```

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.

```php
echo foo(y: $b, x: $a); // 2
```

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.

```php
/*
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);
```

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

```php
echo foo(x: $b, x: $a);
// Fatal error: Uncaught Error: Named parameter $x overwrites previous argoument in ...
```

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

```php
echo foo($a, y: $b); // 2
```

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

```php
echo foo($a, x: $b);
// Fatal error: Uncaught Error: Named parameter $x overwrites previous argoument in ...
```

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

```php
$arr = ['y': 3, 'x' => 6];
echo foo(...$arr); // 2
```

> Good work 👨‍💻