33

Is it possible in PHP to specify a named optional parameter when calling a function/method, skipping the ones you don't want to specify (like in python)?

Something like:

function foo($a, $b = '', $c = '') {
    // whatever
}


foo("hello", $c="bar"); // we want $b as the default, but specify $c
mickmackusa
  • 37,596
  • 11
  • 75
  • 105
Stefano Borini
  • 132,232
  • 95
  • 283
  • 413
  • 2
    Actually in your code sample, the `$c="bar"` is assigning `bar` to a `$c` in *the caller scope* (not in the called function `foo`) and then passing the assigned value to `foo()` as the second parameter, which will be received as the local `$b` variable. – Petruza Aug 11 '11 at 14:42
  • 1
    Named arguments are available only since PHP 8.0. https://stackoverflow.com/a/64072408/7082164 – Jsowa Sep 25 '20 at 23:29

17 Answers17

22

No, it is not possible (before PHP 8.0): if you want to pass the third parameter, you have to pass the second one. And named parameters are not possible either.


A "solution" would be to use only one parameter, an array, and always pass it... But don't always define everything in it.

For instance :

function foo($params) {
    var_dump($params);
}

And calling it this way : (Key / value array)

foo([
    'a' => 'hello',
]);

foo([
    'a' => 'hello',
    'c' => 'glop',
]);

foo([
    'a' => 'hello',
    'test' => 'another one',
]);

Will get you this output :

array
  'a' => string 'hello' (length=5)

array
  'a' => string 'hello' (length=5)
  'c' => string 'glop' (length=4)

array
  'a' => string 'hello' (length=5)
  'test' => string 'another one' (length=11)

But I don't really like this solution :

  • You will lose the phpdoc
  • Your IDE will not be able to provide any hint anymore... Which is bad

So I'd go with this only in very specific cases -- for functions with lots of optional parameters, for instance...

Ricardo Martins
  • 5,238
  • 2
  • 34
  • 53
Pascal MARTIN
  • 385,748
  • 76
  • 642
  • 654
  • Sorry for the OT, but do you know any editor that provides code hints for PHP? any for MacOS? thanks! – Petruza Aug 11 '11 at 14:44
  • code hints ? I would say all IDE do (netbeans, Eclipse PDT, PHPStorm, ...) ; no idea about Mac ; but those might work on Mac – Pascal MARTIN Aug 11 '11 at 16:59
  • @Petruza in case you have not found it yet, try Coda for MacOS. Does a nice job of hinting for PHP. – EFC Dec 11 '12 at 04:40
  • 1
    PHP 8.0 now supports named arguments https://stackoverflow.com/a/19127007/6577664 – Joshua Jun 10 '21 at 23:48
14

PHP 8 was released on November 26, 2020 with a new feature called named arguments.

In this major version release, "named parameters" (aka "named arguments") afford developers some really cool new techniques when calling native and custom functions.

The custom function in this question can now be called with the first parameter (because there is no default for it) and then only the third parameter passed by using named parameters like this: (Demo)

function foo($a, $b = '', $c = '') {
    echo $a . '&' . $b . '&' . $c;
}

foo("hello", c: "bar"); 
// output: hello&&bar

Notice that the second parameter did not need to be declared in the function call because it has a default value defined -- the default value is automatically used within the function body.

Part of the beauty of this new feature is that you don't need to be careful about the order of your named parameters -- the order of their declaration is irrelevant. foo(c: "bar", a: "hello"); works just the same. Having the ability to "skip" declarations and write declarative parameters will improve the readability of your scripts. The only downside of this new feature is that there will be a little bit more bloat in the function calls, but I (and many others) think the benefits outweigh this "cost".

Here is an example of a native function omitting the limit parameter, writing the parameters out of their normal order, and declaring a reference variable. (Demo)

echo preg_replace(
         subject: 'Hello 7',
         pattern: '/[a-z ]/',
         count: $counted,
         replacement: ''
     )
     . " & " . $counted;
// output: H7 & 5

There is more to tell about this new feature. You can even use an associative array to pass the named parameters to the function where the spread/splat operator can be used to unpack the data!

(*notice the slight difference in declaring the reference variable.) (Demo)

$params = [
    'subject' => 'Hello 7',  // normally third parameter
    'pattern' => '/[a-z ]/', // normally first parameter
    // 'limit'               // normally fourth parameter, omitted for this demonstration; the default -1 will be used
    'count' => &$counted,    // normally fifth parameter
    //         ^-- don't forget to make it modifiable!
    'replacement' => '',     // normally second parameter
];
echo preg_replace(...$params) . " & " . $counted;
// same output as the previous snippet

For more information, here are a few leads that explain further about this feature and some common related errors: (I have no affiliation with the following sites)

mickmackusa
  • 37,596
  • 11
  • 75
  • 105
8

No, PHP cannot pass arguments by name.

If you have a function that takes a lot of arguments and all of them have default values you can consider making the function accept an array of arguments instead:

function test (array $args) {
    $defaults = array('a' => '', 'b' => '', 'c' => '');
    $args = array_merge($defaults, array_intersect_key($args, $defaults));

    list($a, $b, $c) = array_values($args);
    // an alternative to list(): extract($args);

    // you can now use $a, $b, $c       
}

See it in action.

Jon
  • 413,451
  • 75
  • 717
  • 787
3

No, it isn't.

The only way you can somewhat do that is by using arrays with named keys and what not.

Alix Axel
  • 147,060
  • 89
  • 388
  • 491
3

As of PHP 5.4 you have shorthand array syntax (not nessecary to specify arrays with cumbersome "array" and instead use "[]").

You can mimic named parameters in many ways, one good and simple way might be:

bar('one', ['a1' => 'two', 'bar' => 'three', 'foo' => 'four']);
// output: twothreefour

function bar ($a1, $kwargs = ['bar' => null, 'foo' => null]) {
    extract($kwargs);
    echo $a1;
    echo $bar;
    echo $foo;
}
Petter Kjelkenes
  • 1,591
  • 1
  • 18
  • 25
  • 1
    But wouldn’t `$bar` be undefined if I called `bar('one', ['x' => 'blah']);`? Even in this scenario, `extract()` seems dangerous and unnecessary… – binki Feb 28 '16 at 04:49
1

It's not exactly pretty, but it does the trick, some might say.

class NamedArguments {

    static function init($args) {
        $assoc = reset($args);
        if (is_array($assoc)) {
            $diff = array_diff(array_keys($assoc), array_keys($args));
            if (empty($diff)) return $assoc;
            trigger_error('Invalid parameters: '.join(',',$diff), E_USER_ERROR);
        }
        return array();
    }

}

class Test {

    public static function foobar($required, $optional1 = '', $optional2 = '') {
        extract(NamedArguments::init(get_defined_vars()));
        printf("required: %s, optional1: %s, optional2: %s\n", $required, $optional1, $optional2);
    }

}

Test::foobar("required", "optional1", "optional2");
Test::foobar(array(
    'required' => 'required', 
    'optional1' => 'optional1', 
    'optional2' => 'optional2'
    ));
David
  • 1,033
  • 2
  • 10
  • 11
1

Normally you can't but I think there a lot of ways to pass named arguments to a PHP function. Personally I relay on the definition using arrays and just call what I need to pass:

class Test{
    public $a  = false;
    private $b = false;
    public $c  = false;
    public $d  = false;
    public $e  = false;
    public function _factory(){
        $args    = func_get_args();
        $args    = $args[0];
        $this->a = array_key_exists("a",$args) ? $args["a"] : 0;
        $this->b = array_key_exists("b",$args) ? $args["b"] : 0;
        $this->c = array_key_exists("c",$args) ? $args["c"] : 0;
        $this->d = array_key_exists("d",$args) ? $args["d"] : 0;
        $this->e = array_key_exists("e",$args) ? $args["e"] : 0;
    }
    public function show(){
        var_dump($this);
    }
}


$test = new Test();
$args["c"]=999;
$test->_factory($args);
$test->show();

live example here: http://sandbox.onlinephpfunctions.com/code/d7f27c6e504737482d396cbd6cdf1cc118e8c1ff

If I have to pass 10 arguments, and 3 of them are the data I really need, is NOT EVEN SMART to pass into the function something like

return myfunction(false,false,10,false,false,"date",false,false,false,"desc");

With the approach I'm giving, you can setup any of the 10 arguments into an array:

$arr['count']=10;
$arr['type']="date";
$arr['order']="desc";
return myfunction($arr);

I have a post in my blog explaining this process in more details.

http://www.tbogard.com/2013/03/07/passing-named-arguments-to-a-function-in-php

Erick
  • 162
  • 8
1

You can keep the phpdoc and the ability to set defaults by passing an object instead of an array, e.g.

class FooOptions {
  $opt1 = 'x';
  $opt2 = 'y';
  /* etc */
};

That also lets you do strict type checking in your function call, if you want to:

function foo (FooOptions $opts) {
  ...
}

Of course, you might pay for that with extra verbosity setting up the FooOptions object. There's no totally-free ride, unfortunately.

Canuck
  • 555
  • 4
  • 13
0

With PHP, the order of arguments is what matters. You can't specify a particular argument out of place, but instead, you can skip arguments by passing a NULL, as long as you don't mind the value in your function having a NULL value.

foo("hello", NULL, "bar");
davethegr8
  • 10,955
  • 5
  • 34
  • 61
  • 2
    NULL is not the default -- not always, and not in this case : here, the default would be an empty string ; and no, NULL and an empty string are not "the same" : see operators === and !==, for instance... – Pascal MARTIN Aug 27 '09 at 18:39
  • Oh, I hadn't realized it was NULL instead of empty, since I'm always using '' as the default and then checking to see if(!$value) – davethegr8 Aug 27 '09 at 20:07
0

If you really really want, try the reflection. And skip with null.

function getDefaultValueByNull($fn, $inputs) {
    $ref = new ReflectionFunction($fn);
    
    $args = array_map(function($p) {
        return [
            $p->getName(),
            $p->isDefaultValueAvailable() ? $p->getDefaultValue() : NULL,
        ];
    }, $ref->getParameters());

    foreach($inputs as $i=>$val) { if ($val!==NULL) $args[$i][1] = $val; }
    
    return array_column($args, 1, 0);
}

function sum($a=9, $b) {
    extract(getDefaultValueByNull(__FUNCTION__, func_get_args()));
    return $a+$b;
}
echo sum(NULL, 1); // 10
Jehong Ahn
  • 1,112
  • 11
  • 23
-1

No not really. There are a few alternatives to it you could use.

test(null,null,"hello")

Or pass an array:

test(array('c' => "hello"));

Then, the function could be:

function test($array) { 
    $c = isset($array[c]) ? $array[c] : '';
}

Or add a function in between, but i would not suggest this:

function ctest($c) { test('','',$c); }
Rene Pot
  • 24,003
  • 7
  • 67
  • 90
-1

I dont think so... If you need to call, for example, the substr function, that has 3 params, and want to set the $length without set the $start, you'll be forced to do so.

substr($str,0,10);

a nice way to override this is to always use arrays for parameters

-1

In very short, sometimes yes, by using reflection and typed variables. However I think this is probably not what you are after.

A better solution to your problem is probably to pass in the 3 arguments as functions handle the missing one inside your function yourself

<?php  
   function test(array $params)
   {
     //Check for nulls etc etc
     $a = $params['a'];
     $b = $params['b'];
     ...etc etc
   }
James Butler
  • 3,834
  • 1
  • 25
  • 38
-1

You can't do it the python way. Anway, you could pass an associative array and than use the array entries by their name:

function test ($args=array('a'=>'','b'=>'','c'=>''))
{
    // do something
}

test(array('c'=>'Hello'));

This doesn't reduce the typing, but at least it's more descriptive, having the arguments' names visible and readable in the call.

Nadir Sampaoli
  • 5,175
  • 4
  • 21
  • 32
-1

Here is a work around:

function set_param_defaults($params) {
  foreach($params['default_values'] as $arg_name => $arg_value) {
    if (!isset($params[$arg_name])) {
      $params[$arg_name] = $arg_value;
    }
  }

  return $params;
}

function foo($z, $x = null, $y = null) {
  $default_values = ['x' => 'default value for x', 'y' => 'default value for y'];
  $params = set_param_defaults(get_defined_vars());

  print "$z\n";
  print $params['x'] . "\n";
  print $params['y'] . "\n";
}

foo('set z value', null, 'set y value');
print "\n";
foo('set z value', 'set x value');

ALTERNATIVELY: Personally I would go with this method.

function foo($z, $x_y) {
  $x_y += ['x' => 'default value for x', 'y' => 'default value for y'];

  print "$z\n";
  print $x_y['x'] . "\n";
  print $x_y['y'] . "\n";
}

foo('set z value', ['y' => 'set y value']);
print "\n";
foo('set z value', ['x' => 'set x value']);

Print outs for both examples.

1st call:

  • set z value
  • default value for x
  • set y value

2nd call:

  • set z value
  • set x value
  • default value for y
Francisco Luz
  • 2,515
  • 2
  • 23
  • 35
-1

Just use the associative array pattern Drupal uses. For optional defaulted arguments, just accept an $options argument which is an associative array. Then use the array + operator to set any missing keys in the array.

function foo ($a_required_parameter, $options = array()) {
    $options += array(
        'b' => '',
        'c' => '',
    );
    // whatever
}

foo('a', array('c' => 'c’s value')); // No need to pass b when specifying c.
binki
  • 6,977
  • 5
  • 63
  • 98
-1

Here's what I've been using. A function definition takes one optional array argument which specifies the optional named arguments:

function func($arg, $options = Array()) {
  $defaults = Array('foo' => 1.0,
                    'bar' => FALSE);
  $options = array_merge($default, $options);

  // Normal function body here.  Use $options['foo'] and
  // $options['bar'] to fetch named parameter values.
  ...
}

You can normally call without any named arguments:

func("xyzzy")

To specify an optional named argument, pass it in the optional array:

func("xyzzy", Array('foo' => 5.7))
Ville Laurikari
  • 27,465
  • 7
  • 58
  • 55