PHP Functions - Arguments

Chapter 34 30 mins

Learning outcomes:

  1. Variadic functions and variadic parameters
  2. Argument unpacking using ...
  3. Default-valued parameters
  4. Reference parameters (&$param)

Introduction

In the previous chapter, we learnt about a great deal of ideas related to functions in PHP. In particular, we saw the local-variables-only design feature of PHP functions; what are callables; the is_callable() function; static local variables; and much more.

Now, it's time to dive deeper into one of the ideas intrinsically related to functions in all programming languages — parameters/arguments.

In this chapter, we shall discover a great deal of information regarding parameters and arguments in functions in PHP. We'll see what are variadic functions and the variadic operator (...); how to create default-valued parameters; and even get a recap of reference parameters.

There is a lot to learn. So without further ado, let's dive right in.

Variadic functions

Many mainstream programming languages today support the idea of creating variadic functions. PHP is no way behind.

A variadic function is a function that can accept a variable number of arguments.

In PHP, a familiar example would be of the built-in function printf(), which we saw in detail in the PHP Strings — Format Strings chapter.

After the first argument, which specifies the format string to be interpolated by the function, we can specify any number of arguments to printf(). Hence, we say that printf() is a variadic function.

Creating a variadic function in PHP just boils down to defining a parameter in it to be variadic. And that's done by preceding the parameter's name in the function's definition with three dots, as in .... This makes the parameter a variadic parameter.

Syntactically, here's what it looks like:

function function_name(...$param, ...) { ... }

The ... preceding $param makes this parameter a variadic parameter.

In JavaScript, such a parameter is called a rest parameter following from the fact that it encapsulates all the rest of the arguments passed from that point onwards.

When a parameter is preceded by ..., i.e. it's a variadic parameter, it holds an array containing all the arguments passed into the function from that point onwards.

At the time of invoking a function having a variadic parameter, PHP automatically collects all the arguments passed onwards from the point of that parameter inside an array, and then provides this array to the parameter.

After all, in order to work with a variable number of arguments, it's quite apparent that at some point, we have to operate a single entity holding all these arguments together; and what could be more intuitive than using an array to do so.

Let's see an example

In the following code, we define a variadic function sum() that computes the sum of all the numbers passed into it and then returns the result:

<?php

function sum(...$nums) {
   $sum = 0;
   foreach ($nums as $num) {
      $sum += $num;
   }
   return $sum;
}

Since it's variadic, we can call $sum with any number of arguments:

<?php

function sum(...$nums) { /* ... */ }

echo 'sum(10, 20): ' .  sum(10, 20), "\n";
echo 'sum(1, 2, 3): ' .  sum(1, 2, 3), "\n";
echo 'sum(0, 1, 10, -5): ' .  sum(0, 1, 10, -5), "\n";
sum(10, 20): 30 sum(1, 2, 3): 6 sum(0, 1, 10, -5): 6

It's even possible to provide absolutely nothing to sum(). In this case, the variadic parameter $nums would be an empty array:

<?php

function sum(...$nums) { /* ... */ }

// It's possible to provide nothing.
echo 'sum(): ' .  sum(), "\n";
sum(): 0

Note that it's not necessary to have just one parameter in a variadic function; we can have any number of parameters.

In the following code, we define a variadic function show_student_info():

<?php

function show_student_info($name, ...$marks) {
   echo "Student name: $name\n";
   echo "Marks: " . implode(', ', $marks) . "\n";
}

The first argument specifies the name of the student whereas all subsequent arguments specify by a collection of numbers representing the student's marks in a series of exams. This collection of marks is represented by the $marks variadic parameter.

As stated before, since it's a variadic parameter, $marks would be an array containing all of the arguments passed after the first one when calling show_student_info(). In the code above, we convert this array into a string using implode(), delimiting the entries with the ', ' substring.

Let's now try calling this function with different sets of arguments:

<?php

function show_student_info($name, ...$marks) {
   echo "Student name: $name\n";
   echo "Marks: " . implode(', ', $marks) . "\n";
}

show_student_info('Alice', 67, 80, 80, 91);
echo "\n";
show_student_info('Bob', 85, 90, 95);
Student name: Alice Marks: 67, 80, 80, 91 Student name: Bob Marks: 85, 90, 95

As you can see, in each call to show_student_info(), the name of the student goes into $name while the remaining arguments get stashed into the $marks array. Simply amazing.

At this stage, it's worthwhile noting that we can NOT have more than one variadic parameter in a PHP function.

This is because, theoretically, the first of them would already encapsulate all of the remaining arguments, leaving nothing for the subsequent ... parameter; and so having another such parameter doesn't make sense.

Failing to do so while defining a function leads to a syntax error:

<?php

function variadic(...$a, ...$b) {
   // Nothing to do here.
}
Fatal error: Only the last parameter can be variadic in <file> on line 3

Moreover, a variadic parameter must be the last parameter of a function.

The reason for this is the exact same as the reason for not allowing more than one variadic parameter — if the variadic parameter ain't the last parameter, it will eat up all the arguments, leaving nothing for the following parameters.

Failing to do so also leads to a syntax error:

<?php

function variadic(...$a, $b) {
   // Nothing to do here.
}
Fatal error: Only the last parameter can be variadic in <file> on line 3

You'll notice that the error message here is exactly the same as the one in the previous example.

This is because having more than one variadic parameter in a function is just a special case of having a parameter following a variadic parameter, both of which are obviously invalid.

Moving on, while skimming through old PHP code, you might come across variadic functions implemented without using variadic parameters. The snippet below details why is this the case.

Before the advent of variadic parameters in PHP

Prior to PHP 5.6, which introduced the ability to create variadic function using variadic parameters, variadic functions were still a thing in PHP. Their implementation, however, was very scratchy.

In this regard, the following three functions were used to make a function variadic: func_num_args(), func_get_args() and func_get_arg(). They still exist in PHP and can only be called inside a given function:

  • func_num_args() returns the total number of arguments sent into the current function.
  • func_get_args() returns an array containing all the arguments sent into the current function.
  • func_get_arg() returns the value of a given argument, identified by its position in the function's argument list.
  • So, for example, the sum() function above could be redefined using func_get_args() as follows:

    <?php
    
    function sum() {
       $sum = 0;
       foreach (func_get_args() as $num) {
          $sum += $num;
       }
       return $sum;
    }

    Notice how we ought to keep the parameter list of the function empty while taking this approach.

    We can even combine func_num_args() and func_get_arg() together and use them instead of func_get_args().

    Here's the sum() function rewritten using func_num_args() and func_get_arg():

    <?php
    
    function sum() {
       $sum = 0;
       for ($i = 0; $i < func_num_args(); $i++) {
          $sum += func_get_arg($i);
       }
       return $sum;
    }

    Iteration happens as many times as there are arguments, governed by func_num_args(). In each iteration, the current argument is obtained via func_get_arg(), providing it with the positional index of that argument which is simply $i.

    Now although both these code snippets work fine and do indeed give a variadic nature to sum(), they're NOT recommended at all.

    You must prefer the variadic parameter syntax, which is much more readable and simple, over the anachronistic approach of using one or more of the functions func_num_args(), func_get_args() and func_get_arg() in order make a function variadic.

    There's a really nice discussion on wiki.php.net regarding the motivation behind the variadic parameter syntax in PHP, along with some commenable examples: PHP RFC: Syntax for variadic functions.

Argument unpacking

To complement the notion of variadic parameters in PHP, which is essentially to convert a list of arguments into an array, we have a feature of argument unpacking.

Argument unpacking is quite similar to the idea of array unpacking in PHP.

Argument unpacking is to transform an array (or traversable) into a list of arguments for a given function.

To unpack an argument, which must either be an array or a traversable (more on that later in this course), we use the spread operator denoted as ....

Unpacking the argument takes each entry therein and makes it an argument for the underlying function.

Let's see an example.

Say we have the variadic function sum() as defined above, which obviously works on a list of arguments. Now suppose that we receive an array from another part of a program and wish to perform this same sum() function on the individual elements of that array.

<?php

function sum(...$nums) { /* ... */ }

// Imagine the following array to be obtained from another
// part of the program.
$external_nums = [1, -5, 0, 9];

How to do this?

Well, no need to worry — we can simply unpack the array, at the time of invoking sum(), into a list of arguments, as follows:

<?php

function sum(...$nums) { /* ... */ }

$external_nums = [1, -5, 0, 9];
echo sum(...$external_nums);
5

The function sum() can never really get to know that we use its arithmetic addition functionality indirectly on an array of numbers. Believe it, that's an ingenious idea.

Moving on, it's possible to have multiple unpacking operations while calling a function.

In the following code, we have two separate arrays of numbers, $evens and $odds, and call sum() by unpacking both of them into argument lists:

<?php

function sum(...$nums) { /* ... */ }

$evens = [2, 4, 6];
$odds = [1, 3, 5];

echo sum(...$evens, ...$odds);
21

Furthermore, as stated earlier, the operand to the spread operator (...) must either be an array or a traversable; provide any other value and you'll get an error.

This can be seen as follows:

<?php

function sum(...$nums) { /* ... */ }

$num = 10;
echo sum(...$num);
Fatal error: Uncaught TypeError: Only arrays and Traversables can be unpacked in <file>:13

The error message generated is quite self-explanatory in describing the issue with the code: we are trying to unpack an integer, which is not possible.

Let's now spare us a few minutes getting to know about an erstwhile way of doing what argument unpacking does, employing the built-in function call_user_func_array().

Before the advent of argument unpacking in PHP

Just like there was a way to implement variadic parameters in PHP before the introduction of the much simplified variadic parameter syntax, there was also a way to implement the conversion of an array into a list of arguments.

That was using the call_user_func_array() function. It still exists in PHP at the time of this writing.

As the nomenclature suggests, call_user_func_array() allows us to call a user-defined function while converting an array into a list of arguments for the function.

The function to call goes as the first argument of call_user_func_array() whereas the second argument specifies an array which gets transformed into a list of arguments for that function.

The return value of call_user_func_array() is the return value of the function that it calls.

Following is example of call_user_func_array():

<?php

function sum(...$nums) { /* ... */ }

$external_nums = [1, -5, 0, 9];
echo call_user_func_array('sum', $external_nums);
5

The first argument specifies the name of the function to call and the second argument specifies the array to convert into an argument list.

Akin to how using the variadic parameter syntax is encouraged over the old approach of implementing variadic functions in PHP, the use of argument unpacking (via the spread operator) is encouraged over the old call_user_func_array() approach.

Default-valued parameters

Often times, you'll want to create functions that are lenient when they aren't provided with arguments for given parameters; in that case, the parameters should take on some default values.

For instance, given the greet() function below

<?php

function greet($name) {
   echo "Hello $name!\n";
}

you might well say that let's allow the user to call greet() without providing a name, in which case a default name 'programmer' should be used.

Currently, in the code above, it's impossible to leave off the first argument to greet() as demonstrated below:

<?php

function greet($name) {
   echo "Hello $name!\n";
}

// We can't leave off the first argument!
greet();

PHP will complain about providing less arguments to the function greet() than it's defined to accept.

Fatal error: Uncaught ArgumentCountError: Too few arguments to function greet(), 0 passed in <file> on line 8 and exactly 1 expected in <file>:3

So how do we achieve such functionality? That is, how to make a parameter optional?

Well, what we ask for is a default-valued parameter, sometimes simply referred to as a default parameter.

A default-valued parameter is a parameter that uses a default value in case it's not provided an argument at the time of the function's invocation.

The syntax of defining default-valued parameters is simple and consistent with other programming languages that support them. That is, the parameter's name in the function's definition is followed by an equals sign (=), followed by the default value.

Something along the following lines:

function function_name($param_name = value, ...) { ... }

If, while calling the function, an argument is provided to the parameter $param_name, well and good. But if it ain't provided an argument, then the default value, value, is used instead.

With this, let's now get back to our greet() function and make its $name parameter optional by providing it a default value:

<?php

function greet($name = "programmer") {
   echo "Hello $name!\n";
}

And let's now test the function:

<?php

function greet($name = "programmer") {
   echo "Hello $name!\n";
}

greet('Alice');
greet();
Hello Alice! Hello programmer!

In the first call, we invoke the function with an argument 'Alice' and, likewise, get the greeting made to Alice. However, in the second call, we provide no argument and thus get the greeting message 'Hello programmer' as the value of $name becomes 'programmer' in this case.

Isn't this awesome?

While using default parameters in PHP, there are certain things to be wary of:

  1. The default value assigned must be a literal or an expression involving literals and basic operators.
  2. The default value of a parameter can't access a preceding parameter (as is otherwise possible in JavaScript).

Reference parameters

We saw reference parameters in action all the way back in the PHP Foundation unit, specifically in the PHP References chapter. In this section, we aim to get a quick recap of them and then see some other traits related to what we've learnt thus far in this chapter.

By default, all values in PHP, when they are passed into functions as arguments, are copied onto the corresponding parameters.

For scalar types such as integers, floats, strings, Booleans, etc. this might not be surprising — all languages follow a similar notion. However, for arrays, it might surely be surprising.

That is, when an array is provided to a function in PHP, whereby the function modifies that array, the modification won't be applied to the original array if the parameter that receives the array is a normal parameter. An example follows:

<?php

function change_first_element($arr) {
   $arr[0] = 10;
}

$nums = [1, 2, 3];
change_first_element($nums);

print_r($nums);

The function change_first_element() takes an array and mutates its first element to the number 10. We try invoking this function on the $nums array and then print the array in order to see whether it has been modified or not.

Here's the output:

Array ( [0] => 1 [1] => 2 [2] => 3 )

As can be seen, the array is not modified.

This is because the $arr parameter of change_first_element() is a normal parameter and, likewise, receives a copy of the array that we provide to change_first_element(). Thereby, modifications to $arr in the function don't apply to the original array.

In order to gain access to the original array passed into the $arr parameter, we have to make it a reference parameter.

To learn more about what exactly is a reference parameter, refer to PHP References.

To make a parameter a reference parameter, all we ought to do is to precede the parameter's name with an ampersand (&).

In general, this could be expressed as follows:

function function_name(&$param, ...) { ... }

Now, any time the function is called and an argument provided to it for this reference parameter, PHP will automatically create a reference to the original value and store this reference in the parameter.

Let's use this knowledge to make the change_first_element() function above to behave the way we expect it to:

<?php

function change_first_element(&$arr) { $arr[0] = 10; } $nums = [1, 2, 3]; change_first_element($nums); print_r($nums);

With an ampersand (&) preceding $arr in the definition of change_first_element() here, it becomes a reference parameter.

Now, when we call the function with $nums as an argument, $arr would not hold a copy of $nums but instead hold a reference to $nums; modifying $arr would make the the modifications be seen in the original $nums array.

The output below confirms this:

Array ( [0] => 10 [1] => 2 [2] => 3 )

Frankly speaking, making intuition of this code, or how references work in general in PHP, isn't that difficult. If you find yourself confused, have a look over the PHP References chapter.

With the recap of reference parameters done, it's time to take note of some points regarding them that are related to the concepts we learnt so far in this chapter.

Here are those points:

  1. A reference parameter can NOT be provided, as argument, a constant value.
  2. A reference parameter can be made a default-valued parameter as well.