skip to content

Functions & Callables

Declaring and calling PHP functions: parameters and named arguments, closures and arrow functions, callables and the pipe operator, generators. Interviewers probe how values are passed and captured.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

20

In PHP 8.5, which values count as a callable, and which older callable forms are deprecated?

level: juniorimportance: must knowfreq 55%

answer

  1. string name, array pair, object, Closure
  2. strings are always fully qualified
  3. 'Class::method' and [Class::class, 'method']
  4. 8.2: self, parent, static in callables
  5. the form $callable() cannot call

basics

~20 s

A PHP callable is a function-name string, a 'Class::method' string, an [object or class, 'method'] array, a Closure, or an invokable object. Since PHP 8.2, relative forms such as 'self::method' or ['parent', 'method'] are deprecated.

solid answer

~40 s

PHP accepts five shapes wherever a `callable` is expected: a **string** naming a function (`'strtoupper'`) or a static method (`'Slugger::clean'`); an **array** of an object or class name plus a method name (`[$slugger, 'slug']`, `[Slugger::class, 'clean']`); a **`Closure`**, whether from `function`, `fn` or the `strlen(...)` syntax; and an **object with `__invoke()`**. All of them can be invoked directly as `$callable($arg)`. String names are resolved as fully qualified, so inside a namespace `'shout'` means the global `\shout`, not `App\Text\shout`. PHP 8.2 deprecated the **relative callables** that only `call_user_func()`-style calls understood: `'self::m'`, `'parent::m'`, `'static::m'`, `['self', 'm']` and the like, and `['B', 'A::m']`. They still run in 8.5 but emit `E_DEPRECATED`; use `self::class`, `static::class` or `self::m(...)` instead.

code

php · 29 lines
php
<?php
declare(strict_types=1);

namespace App\Text;

final class Slugger
{
    public function slug(string $s): string { return strtolower(trim($s)); }
    public static function clean(string $s): string { return trim($s); }
}

function shout(string $s): string { return strtoupper($s); }

$slugger = new Slugger();
$forms = [
    'strtoupper',                  // global function
    'App\Text\shout',              // namespaced function: fully qualified
    'App\Text\Slugger::clean',     // "Class::method" string
    [$slugger, 'slug'],            // object + method
    [Slugger::class, 'clean'],     // class + static method
    fn (string $s): string => $s,  // Closure
    shout(...),                    // first-class callable (8.1+)
];
foreach ($forms as $callable) {
    echo '[', $callable(' Title '), ']';
}
// [ TITLE ][ TITLE ][Title][title][Title][ Title ][ TITLE ]

// 'shout' alone would mean \shout(): Error: Call to undefined function shout()

go deeper

for a junior

Recall the shapes a callable can take: function-name string, [object, 'method'] array, Closure and invokable object, and that each can be called as $f().

for a middle

Explain namespace resolution of string callables, scope and visibility for array callables, and exactly which relative forms 8.2 deprecated.

for a senior

Plan the migration of relative callables and string callbacks to first-class callables, and explain why Closure is the type to store callbacks in properties.

for a principal

Decide where a codebase still accepts string or array callables, such as configuration, versus closures everywhere else, weighing flexibility against static analysis.

## What "callable" means in PHP A **callable** is any value PHP can invoke as a function. Functions such as `array_map()`, `usort()` and `preg_replace_callback()` take one, the `callable` type declaration accepts one, and `is_callable()` tests for one. PHP does not have a single function type; instead it recognises several shapes of value. ## The five forms | Form | Example | Invokes | |---|---|---| | function-name string | `'strtoupper'`, `'App\Text\shout'` | a global or namespaced function | | `"Class::method"` string | `'App\Text\Slugger::clean'` | a static method | | array `[object, 'method']` | `[$slugger, 'slug']` | an instance method on that object | | array `[class, 'method']` | `[Slugger::class, 'clean']` | a static method | | `Closure` object | `fn (string $s) => trim($s)`, `trim(...)` | the closure | | invokable object | `new TitleNormalizer()` | its `__invoke()` method | Every form can be called directly: `$callable(' Title ')`, including strings with `::` and both array forms. Language constructs such as `echo`, `isset`, `empty` or `unset` are not functions, so their names are not callables; wrap them in a function when you need one. ## Namespaces and string callables A string callable is resolved at call time with **no namespace context**. Inside `namespace App\Text;`, the call `shout($s)` finds `App\Text\shout()`, but the string `'shout'` only finds a global `\shout()`, so `array_map('shout', $titles)` throws `Error: Call to undefined function shout()`. Write the fully qualified name, `'App\Text\shout'` or `__NAMESPACE__ . '\shout'`, or avoid the string entirely with `shout(...)`, which the compiler resolves like a normal call. ## Visibility is checked where the call happens String and array callables carry no scope. `[$this, 'privateMethod']` works inside the class, but if the array is returned and invoked from outside, PHP throws `Error: Call to private method ... from global scope`. A closure created inside the class, for example `$this->privateMethod(...)`, keeps the scope it was created in and works anywhere. ## Relative callables: deprecated since PHP 8.2 Some callables only ever worked through the callback machinery (`call_user_func()`, `array_map()`, `is_callable()`, the `callable` type) and never through `$callable()`. PHP 8.2 deprecated them: - strings `"self::method"`, `"parent::method"`, `"static::method"`; - arrays `["self", "method"]`, `["parent", "method"]`, `["static", "method"]`; - arrays whose method part names another class: `["Foo", "Bar::method"]`, `[new Foo, "Bar::method"]`. In PHP 8.5 they still work but raise `Deprecated: Use of "self" in callables is deprecated` or `Deprecated: Callables of the form ["B", "A::foo"] are deprecated`. Even `is_callable()` raises the notice while returning `true`. Ordinary forms such as `"A::method"` and `["A", "method"]` are **not** affected. Replacements: 1. `[self::class, 'method']` or `[static::class, 'method']` for static methods, choosing `static::class` when late static binding is intended; 2. `[$this, 'method']` for instance methods; 3. best, a first-class callable such as `self::method(...)`, `static::method(...)` or `$this->method(...)`, which is checked when it is created and understood by static analysers. ## The `callable` type and its limits The `callable` type declaration can be used for parameters and return values but **not for properties**, because whether a string or array is callable depends on the scope it is used from. To store a callback in a property, type it `Closure` and convert on the way in (`$this->step = $step(...)` for a callable held in a variable). ## Choosing a form in modern code - Prefer **closures**: `fn` for inline logic, `name(...)` for existing functions and methods. - Keep **string and array callables** for configuration data and for code that must stay compatible with them, and write strings fully qualified. - Replace every relative callable before the deprecation turns into removal. ## Traps interviewers like to probe - **Existence is checked late.** A string or array callable is only a value; a typo surfaces when something finally calls it, often deep inside a library. - **Static methods through an instance.** `[$slugger, 'clean']` also works for a static method, but it hides the fact that no instance is needed; `[Slugger::class, 'clean']` states it. - **`is_callable()` depends on where you ask.** The same array can be callable inside a class and not outside it. - **Invokable objects are callables, not closures.** They pass a `callable` check but fail a `Closure` type declaration until converted with `$obj(...)`.

  • In PHP 8.5, what should replace the deprecated callable 'static::create' inside a class?
    Either `[static::class, 'create']`, which keeps late static binding by resolving the class name first, or the first-class callable `static::create(...)`, which produces a `Closure` checked at creation time. Both avoid the `Use of "static" in callables is deprecated` notice. `[self::class, 'create']` is only equivalent if late static binding was not intended.
  • Why does is_callable([$this, 'helper']) return true inside a class but the same array fail when a caller outside invokes it?
    Array and string callables carry no scope; visibility is checked wherever the check or call happens. Inside the class a private `helper()` is accessible, so `is_callable()` is true there. Invoked from outside, the same array throws `Error: Call to private method`. Returning `$this->helper(...)` instead gives the caller a closure that keeps the class scope.

saying these in an interview costs you the question

  • Inside a namespace, the string 'helper' resolves to the current namespace's helper().
  • 'self::method' is the recommended way to pass a static method as a callback.
  • Array callables like [$obj, 'method'] cannot be called directly with $f().
  • Relative callables stopped working in PHP 8.2.
  • A property can be typed callable to store any callback.
open as a page

In PHP, how does an anonymous function reach variables of the enclosing scope through use, and what changes with use (&$x)?

level: juniorimportance: must knowfreq 66%

basics

~20 s

A function () closure sees no outer variables unless listed in use. use ($x) copies the value when the closure is created; use (&$x) binds the variable itself, so later changes flow both ways between closure and outer scope.

open as a page

In PHP, what does calling a function that contains yield return, and when does the code in its body actually run?

level: juniorimportance: must knowfreq 55%

basics

~20 s

A function containing yield returns a Generator object when called, without running its body. Each foreach pull runs the body to the next yield, which hands out a value and pauses with the locals intact.

open as a page

In PHP, how do default parameter values work, and what happens when an optional parameter is declared before a required one?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A default applies only when the caller omits that argument; an explicit null is passed as null. Defaults must be constant expressions. An optional parameter declared before a required one is deprecated since PHP 8.0 and treated as required.

open as a page

In PHP, how do fn arrow functions capture outer variables compared with function () use (), and what can an arrow function not do?

level: middleimportance: must knowfreq 56%

basics

~20 s

An fn arrow function (PHP 7.4+) is a one-expression closure that captures every outer variable its body uses, automatically and by value. It has no use clause, so it cannot write to outer variables or contain statements.

open as a page

In PHP, how would you stream a 2 GB access log line by line with generators, and what pulls it all back into memory?

level: middleimportance: must knowfreq 60%

basics

~20 s

Yield each fgets() line from a generator that opened the log with fopen(), chain filter stages as further generators, and aggregate counters in the final foreach. file(), iterator_to_array() or storing every line reloads the whole file.

open as a page

In PHP 8, how do named arguments let a caller skip optional parameters, and which errors can a named-argument call raise?

level: middleimportance: must knowfreq 58%

basics

~20 s

Since PHP 8.0 a call can pass name: value, matched by parameter name, so any optional parameter can be skipped in any order. An unknown or repeated name throws Error; a positional argument after a named one fails at compile time.

open as a page

In PHP, what does a by-reference parameter such as &$param do, and what happens if you pass it a literal or an expression?

level: juniorimportance: should knowfreq 50%

basics

~20 s

A parameter declared &$param is bound to the caller's variable, so assignments inside the function change it. Only variables and returned references qualify: a literal throws Error, and a plain function result draws a notice with the change lost.

open as a page

In PHP 8.1+, what does first-class callable syntax like strlen(...) create, and why prefer it over 'strlen' or [$obj, 'method'] callables?

level: middleimportance: should knowfreq 45%

basics

~20 s

strlen(...) (PHP 8.1+) creates a Closure from the function or method it names, resolving it right there with the current scope. Typos fail on that line, private methods can be handed out, and IDEs and static analysers can follow it.

open as a page

In PHP, what do is_callable(), call_user_func() and call_user_func_array() do, and when is $callable(...$args) the better choice?

level: middleimportance: should knowfreq 38%

basics

~20 s

is_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.

open as a page

In PHP 8.5, what does the |> pipe operator do, and what must the callable on its right-hand side satisfy?

level: middleimportance: should knowfreq 40%

basics

~20 s

PHP 8.5's |> passes its left value as the only argument to the callable on its right, so chains read in execution order. That callable must need one argument, not take it by reference, and an arrow function there must be parenthesized.

open as a page

In PHP, what do Closure::bind(), bindTo(), call() and fromCallable() do, and how do they let a closure reach private members?

level: middleimportance: should knowfreq 30%

basics

~10 s

bindTo() and Closure::bind() return a copy of a closure with a new $this and class scope; the scope grants private access. call() binds temporarily and invokes. fromCallable() wraps a callable using the caller's scope.

open as a page

In PHP, why does a second foreach over the same Generator object throw, and how do you iterate the sequence twice?

level: middleimportance: should knowfreq 40%

basics

~20 s

A Generator is forward-only: foreach calls rewind(), which is allowed only before the generator passes its first yield, and a finished generator cannot be traversed at all. Iterate twice by calling the generator function again.

open as a page

In PHP, what does yield from do with an inner generator's values, keys and return value?

level: middleimportance: should knowfreq 32%

basics

~20 s

yield from hands every value of an inner generator, Traversable or array to the outer generator's consumer with the inner keys unchanged, and the yield from expression evaluates to the inner generator's return value. Duplicate keys therefore appear easily.

open as a page

In PHP, how do ...$args variadic parameters and ... argument unpacking work, and how do they interact with named arguments?

level: middleimportance: should knowfreq 42%

basics

~20 s

A ...$args parameter collects the remaining arguments into an array; ... at a call spreads an array or Traversable into arguments. String keys unpack as named arguments, and extra named arguments land in the variadic under string keys.

open as a page

In PHP, when does a closure bind $this automatically, and why would you make event-listener closures static in a long-running worker?

level: seniorimportance: should knowfreq 34%

basics

~20 s

A closure created inside a non-static method binds $this automatically, keeping that object alive as long as the closure lives. A static closure binds no object: it holds only what it captures and refuses later binding to an instance.

open as a page

In PHP, how do Generator::send(), getReturn() and throw() work, and what does send() do to a generator that has not started yet?

level: seniorimportance: should knowfreq 28%

basics

~20 s

send($v) makes the paused yield evaluate to $v, resumes the body and returns the next yielded value, first running an unstarted generator to its first yield. getReturn() reads the return value after the end; throw() raises an exception at the paused yield.

open as a page

Why is renaming a function or method parameter a breaking change since PHP 8.0, and how do you keep parameter names stable in a library?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Since PHP 8.0 callers can pass arguments by name, so a parameter name is public API: renaming it makes existing named calls throw Error: Unknown named parameter. PHP never checks names across inheritance, so authors must keep them stable.

open as a page

A PHP 8.5 title pipeline built with |> throws TypeError: strtolower(): Argument #1 ($string) must be of type string, bool given; what went wrong, and how should pipe steps be designed?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

A pipe feeds each step's return value into the next, so a validator returning true or false replaced the title with a bool. Steps must return the value for the next step; validators return their input or throw, since |> never short-circuits.

open as a page

In PHP, why can a helper function called inside a generator not yield on the generator's behalf, and what do Fibers change about that?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

A yield pauses only the function it is written in, so a yielding helper becomes a separate generator that every caller must yield from. A PHP 8.1 Fiber has its own call stack, so Fiber::suspend() pauses it from any depth.

open as a page