11

Goal

I would like to write a function with variable number of parameters (using ...) that calls another function with the same arguments and a new one at the end. Order is important! The example below is just for demonstration.

What I tried

function foo(...$params) {
    $extraVariable = 6;
    var_dump(...$params, $extraVariable);
}
foo(2, 4, 1, 4);

Problem

When I run it, I get the following error message:

PHP Fatal error: Cannot use positional argument after argument unpacking in /home/user/main.php on line 3

How can I achieve my goal?

totymedli
  • 29,531
  • 22
  • 131
  • 165
  • 4
    I would be thankful if the downvoters would point out the problems with my question. – totymedli Apr 11 '18 at 19:31
  • Possible duplicate of [Reference - What does this error mean in PHP?](https://stackoverflow.com/questions/12769982/reference-what-does-this-error-mean-in-php) – Mike Doe Mar 31 '19 at 16:55
  • 5
    @emix How on Earth is this a duplicate of that? That thread doesn't contain anything even slightly related to argument unpacking. – totymedli Mar 31 '19 at 21:21

4 Answers4

17

tl;dr

Unpacking after arguments is not allowed by design, but there are 3 workarounds:

  • Create an array from the new element and unpack that as Paul suggested:

      function foo(...$params) {
          $extraVariable = 6;
          var_dump(...$params, ...[$extraVariable]);
      }
    
  • Push the new element to the params:

      function foo(...$params) {
          $extraVariable = 6;
          $params[] = $extraVariable;
          var_dump(...$args);
      }
    
  • If the wrapped function has named params, just add the extra argument as a named one as James suggested:

      // needs PHP 8.1
      function foo(...$params) {
          $extraVariable = true;
          array_search(...$params, strict: $extraVariable);
      }
    

Explanation

PHP simply doesn't support this. You can see the unit test that checks this behavior:

--TEST--
Positional arguments cannot be used after argument unpacking
--FILE--
<?php

var_dump(...[1, 2, 3], 4);

?>
--EXPECTF--
Fatal error: Cannot use positional argument after argument unpacking in %s on line %d
totymedli
  • 29,531
  • 22
  • 131
  • 165
5

There is a workaround. You cannot use positional arguments after unpacked one, but you can use several unpacked arguments; so you can just wrap your variable(s) in array literal and unwrap it like this:

var_dump(...$params, ...[$extraVariable]);
Edward Surov
  • 469
  • 4
  • 3
3

See the bolded word?

PHP Fatal error: Cannot use positional argument after argument unpacking in /home/user/main.php on line 3

So use it before unpacking.

var_dump($extraVariable, ...$params);
AbraCadaver
  • 78,200
  • 7
  • 66
  • 87
1

This is now possible with PHP 8.1 and named parameters.

function foo(...$params) {
    $this->otherfunction($params, otherParam: 'some value');
}

foo(2, 4, 1);

In otherfunction() you'll have the expanded values of $params as an array [0 => 2, 1 => 4, 2 => 1], and your $otherParam will have the value "some value".


Note: You stated:

I would like to write a function with variable number of parameters (using ...) that calls another function with the same arguments and a new one at the end

Which the above with named params resolves, but your example uses var_dump(). Spread doesn't work in var_dump() but then you would just dump the array itself as that's what var_dump does.

James
  • 4,644
  • 5
  • 37
  • 48
  • For this to work, you need to name your argument the same as the parameter of the function that will take it. If there is no parameter named that way, this won't work. So in most cases, you have to be in control of the function that will take the argument which greatly reduces usability. Not to mention that this is more verbose than simply wrapping it into an array and then unpacking it. – totymedli Dec 13 '22 at 16:33
  • Not sure I understand "f there is no parameter named that way, this won't work" why would you use a param name that doesn't exist on the called method? The question was "calls another function with the same arguments and a new one at the end". If they don't know what the called method params are then anything could break – James Dec 13 '22 at 17:45
  • Yeah, you are right, I forgot that for most functions you will indeed know the param name. However you can't do `array_merge(...$params, a: [1])` since `array_merge` has no named params. – totymedli Dec 14 '22 at 12:47
  • Let's be honest, there's no perfect clean way to approach this. Some are "ok" but it's all a bit fiddly. Personally I think you should pass individual params (if only a few) or some object that has what would be a lot of params as getters etc – James Dec 14 '22 at 13:52