In PHP, what do is_callable(), call_user_func() and call_user_func_array() do, and when is $callable(...$args) the better choice?
answer
- check from the calling scope
- $syntax_only and &$callable_name
- string keys become named arguments
- call_user_func() cannot pass by reference
- $f(...$args) does the same job
basics
~20 sis_callable() reports whether a value can be called from the current scope. call_user_func() calls it with listed arguments; call_user_func_array() with an array, whose string keys become named arguments since PHP 8.0. Modern code usually writes $callable(...$args) instead.
solid answer
~40 s`is_callable($value)` returns `true` if the value could be called **from the current scope**: it checks that the function or method exists and is visible here. `$syntax_only = true` checks only the shape, and a third by-reference argument receives a readable name like `'Title::clean'`. `call_user_func($cb, ...$args)` invokes a callable with the listed arguments and `call_user_func_array($cb, $args)` with an array; since PHP 8.0 **string keys in that array are named arguments**, where they used to be ignored. `call_user_func()` **cannot pass by reference**: a by-reference parameter gets a copy plus a `must be passed by reference, value given` warning, while `call_user_func_array()` passes array elements that are references. Every callable form can also be invoked directly, so `$callable(...$args)` does the same job with plain syntax, and it is what most modern code uses.
code
php · 25 lines<?php
declare(strict_types=1);
function addSuffix(string &$title, string $suffix): void
{
$title .= $suffix;
}
$title = 'Release notes';
call_user_func('addSuffix', $title, ' (draft)');
// Warning: addSuffix(): Argument #1 ($title) must be passed by reference, value given
echo $title, "\n"; // Release notes (a copy was modified)
call_user_func_array('addSuffix', [&$title, ' (draft)']);
echo $title, "\n"; // Release notes (draft)
$callable = 'addSuffix';
$callable($title, '!');
echo $title, "\n"; // Release notes (draft)!
var_dump(is_callable('addSufix')); // bool(false)
var_dump(is_callable('addSufix', true)); // bool(true): shape only
is_callable([new ArrayObject(), 'count'], false, $name);
echo $name, "\n"; // ArrayObject::countgo deeper
Recall that is_callable() checks whether something can be called, and that call_user_func() and call_user_func_array() call it with arguments.
Explain scope-dependent answers from is_callable(), string keys as named arguments since 8.0, and the by-reference limitation of call_user_func().
Review legacy dynamic-dispatch code for PHP 8 behaviour changes and lost reference writes, and migrate it to direct calls or first-class callables.
Decide how much dynamic dispatch by name a codebase allows, trading configurability against static analysis and safe refactoring.
## `is_callable()` ```php function is_callable(mixed $value, bool $syntax_only = false, &$callable_name = null): bool ``` `is_callable()` answers "could I call this value from here, right now?": - it accepts every callable form: strings, arrays, closures and invokable objects; - for strings and arrays it checks that the function, class and method **exist** and are **visible from the calling scope**, so a private method is callable inside its class and not outside; - for a class with `__call()`, any method name on an instance is reported as callable, because the magic method would handle it; - `$syntax_only = true` only checks that the value has a callable shape (a string, or a two-element array with an object or class string and a method name), without looking anything up; - when the third argument is passed, it receives a printable name such as `'TitleCleaner::normalize'`, useful in log messages; - since PHP 8.2 a deprecated relative callable such as `'self::build'` still returns `true` but triggers the deprecation notice. ## `call_user_func()` and `call_user_func_array()` ```php function call_user_func(callable $callback, mixed ...$args): mixed function call_user_func_array(callable $callback, array $args): mixed ``` Both invoke a callable and return its result. The difference is how arguments arrive: | | `call_user_func()` | `call_user_func_array()` | |---|---|---| | Arguments | listed after the callback | one array | | Named arguments | `call_user_func($cb, 'x', limit: 5)` | string keys: `['x', 'limit' => 5]` | | By-reference parameters | not supported: warning, copy passed | supported when the array element is a reference: `[&$title]` | The **string-key rule changed in PHP 8.0**: before named arguments existed, string keys were silently ignored and the values passed positionally. Now `['suffix' => '!', 'title' => $t]` binds by name, an unknown key throws `Error: Unknown named parameter`, and a positional value after a named one throws `Cannot use positional argument after named argument`. Code built on PHP 7 that passed associative arrays can therefore change behaviour. ## The by-reference trap `call_user_func()` receives its arguments by value, so it has no reference to hand on. When the target declares `&$param`, PHP emits `Warning: addSuffix(): Argument #1 ($title) must be passed by reference, value given`, still calls the function, and the function modifies a temporary copy. The caller's variable is unchanged. Use `call_user_func_array($cb, [&$title])` or, better, call the callable directly. ## Direct invocation Every callable form can be called with normal call syntax: 1. `$callable('x')` works for function names, `'Class::method'` strings, both array forms, closures and invokable objects; 2. `$callable(...$args)` spreads a list, and string keys become named arguments exactly as in `call_user_func_array()`; 3. by-reference parameters work as in any call when `$callable($title)` is written with a variable. So `call_user_func()` and `call_user_func_array()` add nothing for most code. Their remaining uses are narrow: passing them as values themselves (for example `'call_user_func'` as a callback), and the deprecated relative callables, which only the callback machinery ever accepted and which should be migrated anyway. ## Legacy code worth reviewing When upgrading older code to PHP 8.x, dynamic calls are a common source of silent behaviour changes: - `call_user_func_array()` fed with associative arrays, for example rows from a database or a decoded JSON object, now binds by key instead of by position; - arrays that mix numeric and string keys may now throw `Cannot use positional argument after named argument` if a numeric key follows a string key; - `call_user_func()` wrappers around functions with by-reference parameters have been losing writes all along, with only a warning in the log; - callbacks built from `'self::...'` or `['parent', ...]` emit deprecations since 8.2. ## Checking before calling - If a value comes from configuration or user input, validate it with `is_callable()` before invoking, and log the name from its third argument when it fails. - Remember that `is_callable()` says "callable from here": a value that passes inside a class may fail when a different class invokes it. - For parameters, a `callable` or `Closure` type declaration makes PHP perform the check at the call boundary and throw a `TypeError` on failure.
- What changed in PHP 8.0 for call_user_func_array($cb, ['title' => $t, 'suffix' => '!'])?The string keys are now named arguments, so `$t` binds to `$title` and `'!'` to `$suffix` regardless of order. Before 8.0 the keys were ignored and the values passed positionally in array order. A key that matches no parameter now throws `Error: Unknown named parameter`, unless the callable has a variadic parameter to collect it.
- In PHP, can is_callable() return true for a method that does not exist?Yes, when the object's class defines `__call()`: any method name on it is reported callable because the magic method would receive the call. With `$syntax_only = true` it also returns true for any well-shaped string or array without checking existence. So `is_callable()` is a guard against obvious mistakes, not proof the method exists.
saying these in an interview costs you the question
- call_user_func() passes arguments by reference when the function expects them.
- call_user_func_array() ignores string keys and passes values positionally.
- is_callable() returns the same answer from any scope.
- An array callable can only be invoked through call_user_func().
- is_callable($x, true) confirms that the function exists.