Passing named parameters to a php function through call_user_func_array
Asked Answered
F

8

16

When trying to call a function in a child class with an arbitrary set of parameters, I'm having the following problem:

class Base{

    function callDerived($method,$params){
        call_user_func_array(array($this,$method),$params);
    }
}

class Derived extends Base{
    function test($foo,$bar){
        print "foo=$foo, bar=$bar\n";
    }
}

$d = new Derived();
$d->callDerived('test',array('bar'=>'2','foo'=>1));

Outputs:

foo=2, bar=1

Which... is not exactly what I wanted - is there a way to achieve this beyond re-composing the array with the index order of func_get_args? And yes, of course, I could simply pass the whole array and deal with it in the function... but that's not what I want to do.

Thanks

Fosdick answered 7/7, 2011 at 12:25 Comment(8)
I think you are out of luck here...Hypercorrect
No way. Also it feels uncomfortable, that you don't know the order of the arguments of your own methods :X Maybe you should consider using interfaces, if you want to ensure a specific order.Hydrous
@Hydrous Actually, what I'm unsure of is the order of the array of params I get - but yes, this is a kludgy situation.Fosdick
@Wagemage: Maybe you just don't need call_user_func_array(): call_user_func('func', $array['foo'], $array['bar']);?Hydrous
@Hydrous Yeah, I see what you mean - but for different Derived classes the invocation will have a different number and order of parameters... it's a kludge that I'm trying to de-pessimize, not brand-spaking-new code I'm trying to write.Fosdick
I shared a Q/A as a solution stackoverflow.com/questions/56415163Dionnadionne
It is possible now in PHP 8.0. You must only make small change and use spread operator (...) to your code. https://mcmap.net/q/348539/-php-default-function-parameter-values-how-to-39-pass-default-value-39-for-39-not-last-39-parameters-duplicateCtesiphon
@Wag I haven't tried with call_user_func_array(), but the earliest asked question on the topic of "named parameters" was in 2009 and contains a comprehensive/generous answer posted 2020-11-25 (the day before the new feature was deployed with PHP8.0). I consider this page to be the "canonical" on Stack Overflow.Ostrowski
K
24

No. PHP does not support named parameters. Only the order of parameters is taken into account. You could probably take the code itself apart using the ReflectionClass to inspect the function parameter names, but in the end you'd need to use this to reorder the array anyway.

Kinematograph answered 7/7, 2011 at 12:29 Comment(2)
Yeah, was afraid of that. Oh well... aesthetics be damned, I can deal with it through the func_get_args stuff.Fosdick
Now named parameters support in PHP 8 :)Chromatogram
A
8

The stock PHP class ReflectionMethod is your friend.

Example:

class MyClass { 
    function myFunc($param1, $param2, $param3='myDefault') { 
        print "test"; 
    } 
}

$refm = new ReflectionMethod('MyClass', 'myFunc');

foreach ($refm->getParameters() as $p) 
    print "$p\n";

And the result:

Parameter #0 [ <required> $param1 ]
Parameter #1 [ <required> $param2 ]
Parameter #2 [ <optional> $param3 = 'myDefault' ]

At this point you know the names of the parameters of the target function. With this information you can modify your method 'callDerived', and you can re-order the array to call_user_func_array according to the parameter names.

Antisocial answered 20/9, 2013 at 16:12 Comment(0)
R
5

Good news, I had the same concern (I was looking for named arguments in PHP, like Python does), and found this useful tool : https://github.com/PHP-DI/Invoker

This uses the reflection API to feed a callable with some arguments from an array and also use optional arguments defaults for other parameters that are not defined in the array.

$invoker = new Invoker\Invoker;
$result = $invoker->call(array($object, 'method'), array(
    "strName"  => "Lorem",
    "strValue" => "ipsum",
    "readOnly" => true,
    "size"     => 55,
));

Have fun

Red answered 25/6, 2015 at 16:48 Comment(1)
DownVote: That's not called "named arguments". That's just passing a map to function. Named arguments will support code completion, reflection, ...Dionnadionne
S
5

UPDATE: PHP 8 Now supports named parameters. And it works with call_user_func_array if you pass an associative array. So you can simply do this:

<?php

function myFunc($foo, $bar) {
    echo "foo=$foo, bar=$bar\n";
}

call_user_func_array('myFunc', ['bar' => 2, 'foo' => 1]);
// Outputs:  foo=1, bar=2

In your code, you'll be happy to know that you don't have to change a thing. Just upgrade to PHP 8 and it'll work as you expected

Sadiron answered 4/5, 2021 at 7:55 Comment(0)
G
3

You can simply pass an array and extract:

function add($arr){
    extract($arr, EXTR_REFS);
    return $one+$two;
}
$one = 1;
$two = 2;
echo add(compact('one', 'two')); // 3

This will extract as references, so there is close to no overhead.

Gaullism answered 1/3, 2016 at 23:37 Comment(0)
E
3

I use a bitmask instead of boolean parameters:

// Ingredients
define ('TOMATO',    0b0000001);
define ('CHEESE',    0b0000010);
define ('OREGANO',   0b0000100);
define ('MUSHROOMS', 0b0001000);
define ('SALAMI',    0b0010000);
define ('PEPERONI',  0b0100000);
define ('ONIONS',    0b1000000);

function pizza ($ingredients) {
  $serving = 'Pizza with';
  $serving .= ($ingredients&TOMATO)?' Tomato':''; 
  $serving .= ($ingredients&CHEESE)?' Cheese':''; 
  $serving .= ($ingredients&OREGANO)?' Oregano':''; 
  $serving .= ($ingredients&MUSHROOMS)?' Mushrooms':''; 
  $serving .= ($ingredients&SALAMI)?' Salami':''; 
  $serving .= ($ingredients&ONIONS)?' Onions':''; 
  return trim($serving)."\n" ;
}

// Now order your pizzas!
echo pizza(TOMATO | CHEESE | SALAMI); 
echo pizza(ONIONS | TOMATO | MUSHROOMS | CHEESE); // "Params" are not positional
Ensanguine answered 4/12, 2016 at 10:14 Comment(0)
B
1

For those who still might stumble on the question (like I did), here is my approach:

since PHP 5.6 you can use ... as mentioned here:

In this case you could use something like this:

class Base{

    function callDerived($method,...$params){
            call_user_func_array(array($this,$method),$params);
        }
}

class Derived extends Base{
    function test(...$params){
        foreach ($params as $arr) {
            extract($arr);
        }
        print "foo=$foo, bar=$bar\n";
    }
}

$d = new Derived();
$d->callDerived('test',array('bar'=>'2'),array('foo'=>1)); 

//print: foo=1, bar=2
Bowstring answered 5/2, 2017 at 22:37 Comment(0)
J
0

There is a way to do it and is using arrays (the most easy way):

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();

a full explanation can be found in my blog: http://www.tbogard.com/2013/03/07/passing-named-arguments-to-a-function-in-php/

Jed answered 7/3, 2013 at 22:59 Comment(1)
Srly ? Hope you'll never mess-up with something like $this->e = array_key_exists("d",$args) ? $args["e"] : 0;, will be so time-consuming to debug... BTW you're using 7 lines of code just to save a few parameters while calling your function ? Clearly not worth it.Machree

© 2022 - 2024 — McMap. All rights reserved.