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

8
  • 2
    I think you are out of luck here... Commented Jul 7, 2011 at 12:28
  • 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. Commented Jul 7, 2011 at 12:36
  • @KingCrunch Actually, what I'm unsure of is the order of the array of params I get - but yes, this is a kludgy situation. Commented Jul 7, 2011 at 12:44
  • 2
    @Wagemage: Maybe you just don't need call_user_func_array(): call_user_func('func', $array['foo'], $array['bar']);? Commented Jul 7, 2011 at 12:45
  • @KingCrunch 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. Commented Jul 7, 2011 at 12:56

8 Answers 8

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Yeah, was afraid of that. Oh well... aesthetics be damned, I can deal with it through the func_get_args stuff.
Now named parameters support in PHP 8 :)
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.

Comments

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

1 Comment

DownVote: That's not called "named arguments". That's just passing a map to function. Named arguments will support code completion, reflection, ...
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

Comments

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.

Comments

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

Comments

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

Comments

-1

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/

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.