5

I've read other answers on stack pertaining to using call_user_func_array vs just calling the function but I still can't glean when the former should be used. I get that you might want to use call_user_func_array when you don't know how many args are passed thus you can do: $args = func_get_args(); ... but don't you always need to know the arguments if they are to be used in a function?

Both of the following work, I assume the first has less overhead.

$format = new Foo;
$method = 'somemethod';
$arg = 'somevalue';
if (method_exists($format, $method)) {
    return $format->$method($arg);
}

vs

return call_user_func_array(array($format, $method), array($arg));

When will one really benefit from using call_user_func_array?

Alex
  • 9,215
  • 8
  • 39
  • 82
  • I've seen and used it in static classes where an instance of a concrete class has been imported, and it is desired to access the concrete class through static methods. – Brian Gottier Dec 24 '17 at 20:17
  • when your PHP version does not support stored function name calls (5.4?). also `func_get_args` is often used when variadics does not supported (5.6). however i've seen so far its a matter of preference. being consistent is the key. – Chay22 Dec 24 '17 at 20:23
  • Now with argument unpacking it kinda makes this function mute, as that was the *argument* in answers here: https://stackoverflow.com/questions/18526060/why-should-one-prefer-call-user-func-array-over-regular-calling-of-function suppose it just comes down to if you want to support old php versions. – Lawrence Cherone Dec 24 '17 at 20:24
  • @Chay22 what exactly is a "stored function name calls"? – Alex Dec 24 '17 at 20:28
  • @Alex http://php.net/manual/en/functions.variable-functions.php – Lawrence Cherone Dec 24 '17 at 20:31

4 Answers4

3

An example of where call_user_func_array is very useful

Lets say you have a desire to access an object, but through static methods, like this:

Helper::load();

Well, that won't work by itself, because if you look at the concrete class it doesn't have the static method:

class Helper {

  public function load()
  {
      // Do some loading ...
  }

  public function aFunctionThatNeedsParameters( $param1, $param2 )
  {
      // Do something, and $param1 and $param2 are required ...
  }
}

So in a second class, we could do something like this, because the Helper class above is loaded into a dependency injection container (note that these two Helper classes are named the same, but would be in different namespaces):

class Helper extends DIContainer {

  public static $helper_instance = NULL;

  public static function get_instance()
  {
    if( is_null( self::$helper_instance ) )
    {
      self::$helper_instance = parent::$container['helper'];
    }

    return self::$helper_instance;
  }

  public static function __callStatic( $method, $params )
  {
    $obj = self::get_instance();

    return call_user_func_array( [ $obj, $method ], $params );
  }
}

The thing is, there may be another method that needs parameters, even though our load method doesn't have any.

So in this case we can use Helper::load(), but also Helper::aFunctionThatNeedsParameters( $param1, $param2 )

I think this is used a lot in PHP frameworks that know that static classes are not usually appropriate, but they want the ability to call methods as if they were static. I hope this makes sense.

Brian Gottier
  • 4,522
  • 3
  • 21
  • 37
  • Interesting! Why would you desire to call a non-static method as static .. does it have something to do with the dependency injection? – Alex Dec 24 '17 at 20:42
  • 1
    Consider Laravel, arguably the most popular PHP framework, and they call these "Facades", https://laravel.com/docs/5.5/facades . They say the benefit is " providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods." And yes, this does have to do with dependency injection. – Brian Gottier Dec 24 '17 at 20:45
  • so basically, it, among other things, looks nicer – Alex Dec 24 '17 at 20:46
  • I suppose. One benefit of calling the static way would be that you don't have to maintain a chain of dependency injection through what is sometimes a bunch of classes. – Brian Gottier Dec 24 '17 at 20:57
2

David Sklar addressed the question of when to use call_user_func_array in his PHP Cookbook. So, to focus on one comment of the OP:

"...don't you always need to know the arguments if they are to be used in a function?"

Here's an instance where call_user_func() can come in handy:

<?php

function Test(){
  $args = func_get_args();
  call_user_func_array("printf",$args);
}

Test("Candidates 2014: %s %s %s %s\n","Tom","Debbie","Harry", "Sally");
Test("Candidates 2015: %s %s\n","Bob","Sam","Sarah");

See live code

With the pair of call_user_func_array() and func_get_args(), the user-defined function Test() can take a variable number of arguments.

Also, useful for aggregating methods (see PHP Cookbook p208-210) as the following simplified example demonstrates:

<?php
class Address {
   protected $city;

   public function getCity() {
      return $this->city;
   }
   public function setCity($city) {
      $this->city=$city;
   }
}

class Person {
     protected $name="Tester";
     protected $address;

     public function __construct() {
            $this->address = new Address;
     }
     public function getName(){
            return $this->name;
     }
    public function __call($method, $args){
           if (method_exists($this->address,$method)) {
                return call_user_func_array( array($this->address,$method),$args);
           }
    }
}

  $sharbear = new Person;
  echo $sharbear->setCity("Baltimore");
  echo "Name: ",$sharbear->getName(),"\n";
  echo "City: ",$sharbear->getCity();

See live code

Addendum

Since PHP 5.6, PHP supports variadic functions and argument unpacking, so call_user_func() and func_get_args() may be replaced with respect to variable arguments as follows:

<?php

function Test(...$args){

   printf(...$args);
}
$namesT = ["Timmy","Teddy","Theo","Tad"];
$namesB = ["Bobby","Bill","Brad"];

Test("T Names: %s %s %s %s\n",...$namesT);
Test("B Names: %s %s %s\n",...$namesB);

See live code

slevy1
  • 3,797
  • 2
  • 27
  • 33
2

<!-- begin snippet: js hide: false console: true babel: false -->
<pre>
I use call_user_func_array when I need to call a function or php function from my ajax enabled page. 

eg.
in html and javascript:

<form id='my-form'>

<input id='name' value='Wong, Billy'>
<input id='age' value='50'>

<a href='javascript;' onClick="xjx.query('helloWorld', $('#name').val(), $('#age').val())">Show name and age</a>

<a href='javascript;' onClick="xjx.query('showName', $('#name').val())">Show name</a>

<a href='javascript;' onClick="xjx.query('showAge', $('#age').val())">Show age</a>

<a href='javascript;' onClick="xjx.post('#my-form', 'showPost', 'live long and prosper')">Show age</a>

</form>

xjx.query and xjx.post, personal javascript functions of mine will then format and submit to the php page via ajax

in php
$method = $_POST['method'];
$args = $_POST['args'];

call_user_func_array($method, $args)

function helloWorld($name, $age) {
    ....
}

function showName($name) {
    ....
}

function showAge($age) {
    ....
}

function showPost($f, $message) {
   print_r($f);
   echo $message;
}

my libs are found in github with xjx as the project name
</pre>
Billy Wong
  • 31
  • 3
0

let say we have couple of classes

class Request 
{
    private $req;

    public function __construct($req)
    {
        $this->req = $req;
    }

    public function __call($name, $arg)
    {
        return \call_user_func_array(array($this->req,$name), $arg);
    }
}

class Get 
{
    public function load($url)
    {
        // fire an http request
    }
}

class Post 
{
    public function load($url)
    {
        // fire an http request
    }

    public function attachHeader($header=[])
    {
        // attach an header to the request
    }
}

$request_1 = new Request(new Get());
$request_1->load($url);

$request_2 = new Request(new Post());
$request_2->attachHeader($header_data);
$request_2->load($url);

here you can see we can call the attachHeader() method by using of call_user_func_array() without any problem.

i hope it will help

Joy Kumar Bera
  • 314
  • 2
  • 8