0

call_user_func, call_user_func_array, and the callable type

Intermediate5 min read·php-06-012
laravel-src

Concept

PHP's callable type represents anything that can be called as a function. Understanding callable forms is essential for building flexible APIs, implementing callbacks, and reading Laravel's source code — which uses callables extensively in middleware, macros, and container bindings.

Callable forms: (1) A string naming a function: 'strlen' or 'MyClass::staticMethod'. (2) An array: [$object, 'method'] for instance methods, ['ClassName', 'staticMethod'] for static. (3) A Closure (anonymous function). (4) An invokable object (a class with __invoke). (5) A first-class callable syntax (PHP 8.1): strlen(...) — creates a Closure from a function reference.

call_user_func($callable, ...$args) calls a callable with individual arguments. call_user_func_array($callable, $args) calls with an array of arguments — equivalent to $callable(...$args) in modern PHP. Both were the primary way to call callables before PHP 5.4's variable function improvements.

Modern PHP: Variable functions ($fn($arg)) work for closures, invokables, and simple string callables. The spread operator ($fn(...$args)) replaces call_user_func_array. call_user_func / call_user_func_array are still useful when you receive a callable from external code and don't know its form, or for strict callable type checking.

is_callable($val) checks if a value is a valid callable. It also checks method existence and accessibility — useful for validation before invocation.

Code Example

php
<?php
declare(strict_types=1);

// Callable forms
$forms = [
    'strlen',                       // string: built-in function
    [new DateTime(), 'format'],     // array: instance method
    ['DateTime', 'createFromFormat'], // array: static method
    fn(string $s) => strtoupper($s), // Closure
];

foreach ($forms as $callable) {
    var_dump(is_callable($callable)); // true for all
}

// Invokable object
class Multiplier
{
    public function __construct(private int $factor) {}
    public function __invoke(int $n): int { return $n * $this->factor; }
}
$triple = new Multiplier(3);
echo $triple(5);        // 15 — called like a function
echo $triple(5);        // also works via call_user_func
call_user_func($triple, 5);

// First-class callable syntax (PHP 8.1)
$strlenFn = strlen(...); // Closure wrapping strlen
$strlenFn('hello');      // 5

$arrMapFn = array_map(...);
$result = $arrMapFn(fn($x) => $x * 2, [1,2,3]); // [2,4,6]

// call_user_func_array (legacy but still useful)
function add(int $a, int $b, int $c): int { return $a + $b + $c; }
$args = [1, 2, 3];
echo call_user_func_array('add', $args); // 6
// Modern equivalent:
echo add(...$args); // 6

// Higher-order function accepting any callable
function applyTo(array $items, callable $fn): array
{
    return array_map($fn, $items);
}
$result = applyTo([1,2,3], fn($x) => $x ** 2);    // [1,4,9]
$result = applyTo(['a','b','c'], 'strtoupper');      // ['A','B','C']
$result = applyTo(['hello'], [new Multiplier(2), '__invoke']); // won't work for this but pattern works