skip to content

In PHP, what do the __toString and __invoke magic methods let an object do, and when does PHP call each?

level: juniorimportance: should knowfreq 50%

answer

  1. object used as a string
  2. echo, concatenation, (string) cast
  3. strict string parameter rejects the object
  4. object called like a function
  5. is_callable true, fits callable type

basics

~20 s

__toString lets an object act as a string: PHP calls it for echo, concatenation, interpolation, (string) casts and coercive string parameters. __invoke lets an object be called like a function, $obj($x), so it passes callable checks.

solid answer

~40 s

`__toString(): string` defines what an object becomes in a **string context**: `echo $money`, `'Total: ' . $money`, string interpolation, `(string) $money`, and a `string` parameter in coercive mode. Without it, echo, concatenation and casts throw `Error: Object of class Money could not be converted to string`. With `declare(strict_types=1)`, a `string` parameter does **not** call it; the object is rejected with `TypeError`, so accept `string|Stringable` if you want both. It can throw exceptions (since 7.4). `__invoke(...)` makes the instance **callable**: `$handler($request)` runs it, `is_callable($handler)` is `true`, and it satisfies a `callable` type, so it can go to `usort` or `array_map`. It suits single-action classes and comparators that carry configuration in properties.

code

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

final class Money
{
    public function __construct(private int $cents) {}

    public function __toString(): string
    {
        return sprintf('%d.%02d EUR', intdiv($this->cents, 100), $this->cents % 100);
    }
}

function label(string $text): string { return "[$text]"; }

$m = new Money(1999);
echo 'Total: ' . $m, PHP_EOL;       // Total: 19.99 EUR
echo label((string) $m), PHP_EOL;   // [19.99 EUR]
// label($m);                        // TypeError under strict_types

go deeper

for a junior

Recall that __toString controls how an object prints and __invoke lets you call an object like a function.

for a middle

Explain which contexts call __toString, how strict_types changes typed parameters, and why invokables pass callable checks.

for a senior

Keep __toString cheap and unsurprising, and choose invokable classes where named, injectable callbacks help design and testing.

for a principal

Set conventions for value objects and single-action classes so string conversion and callables stay predictable across a codebase.

## `__toString`: objects in string contexts `__toString()` takes no arguments and returns a `string`. PHP calls it whenever an object must become a string: - `echo $obj;` and `print $obj;` - concatenation, `'Total: ' . $obj` - string interpolation, `"Total: $obj"` - an explicit `(string) $obj` cast - comparing the object with a string using `==` - passing the object to a `string` parameter **in coercive mode** (the default, without `strict_types`) If the class has no `__toString`, echo, concatenation, interpolation and the cast throw `Error: Object of class Money could not be converted to string`, a `string` parameter throws `TypeError`, and `==` against a string is simply `false`. ## Strict types change one context `declare(strict_types=1)` makes scalar type declarations strict for calls made from that file. A `string` parameter then rejects an object, even one with `__toString`, with a `TypeError`. `echo`, concatenation and casts are not parameter passing, so they still call `__toString`. When a function should accept both, declare the parameter `string|Stringable` and convert with `(string) $value` inside. Since PHP 8.0 any class declaring `__toString` automatically satisfies the `Stringable` interface. ## Rules for `__toString` 1. It must be non-static and take no parameters, and should be `public` (a narrower visibility draws a warning). 2. If it declares a return type, that type must be `string`. 3. Exceptions thrown inside it propagate normally (allowed since PHP 7.4; earlier versions turned them into a fatal error). 4. In coercive mode, a non-string return value is coerced to string where possible, per standard type rules since PHP 8.0. Good uses: value objects with an obvious textual form (a money amount, an email address, a URL). Poor uses: a string form that hides a database query or can fail often, because string contexts are everywhere and the call is invisible at the call site. ## `__invoke`: objects as functions `__invoke()` runs when an object is **called like a function**: ```php $byName = new CompareBy('name'); usort($users, $byName); // usort calls $byName($a, $b) ``` Facts to know: - It must be `public` and non-static. Its parameters and return type are free, like any method. - `is_callable($obj)` returns `true`, and the object passes a `callable` type declaration. - Built-in functions that take a callback (`usort`, `array_map`, `array_filter`) accept the object directly. - An invokable object keeps state in its properties, which a plain function cannot do without a closure. Common uses: | Use | Why an invokable object fits | |---|---| | Comparators such as `CompareBy('name')` | configuration lives in a property, logic in `__invoke` | | Single-action handlers or controllers | one public entry point, dependencies injected in the constructor | | Middleware and pipeline stages | each stage is a callable object that can also be type-hinted by class | One boundary to remember: an invokable object satisfies a `callable` type but is **not** a `Closure`, so a parameter typed `Closure` rejects it. Convert with `Closure::fromCallable($obj)` when an API insists on a closure. Compared with a closure, an invokable class has a name, can implement interfaces, can be autowired by a container and is easy to find in a codebase. ## Traps interviewers like - **Array keys do not convert.** `$prices[$money] = 1` does not call `__toString`; PHP throws `TypeError: Cannot access offset of type Money on array`. Convert explicitly with `(string) $money` first. - **Built-in string functions follow the file's mode.** `strlen($money)` or `str_contains($money, 'EUR')` call `__toString` in coercive mode but throw `TypeError` under `strict_types`, because they are ordinary typed parameters. - **`implode()` converts each element**, so an array of `Money` objects joins cleanly through `__toString`. - **Loose comparison with a string** (`$money == '19.99 EUR'`) converts the object with `__toString`; `===` compares types and is simply `false`. - **`__invoke` is not a constructor or a static entry point.** `Handler::__invoke()` called statically fails like any instance method; you need an instance, `(new Handler())($x)`. ## Summary - `__toString` is called in string contexts; without it they throw `Error`. - Under `strict_types`, a `string` parameter rejects objects; use `string|Stringable`. - `__invoke` makes an object callable and accepted wherever a `callable` is expected.

  • In PHP with strict_types=1, does passing an object with __toString to a string parameter work?
    No. Strict mode rejects the object with `TypeError`, because only an exact `string` is accepted. Cast explicitly with `(string) $obj`, or declare the parameter as `string|Stringable`. `echo` and concatenation are not affected, because they are not typed parameters.
  • In PHP, how does an invokable object differ from a closure as a callback?
    Both are callable. The invokable object is a named class: it can implement interfaces, hold injected dependencies, be type-hinted by class and be found by name. A closure is anonymous and captures variables from its defining scope instead.

__toString is a name badge the object shows whenever someone needs to write it down; __invoke is a button on the object, so anyone who expects a function can press it.

saying these in an interview costs you the question

  • Says strict_types makes echo $obj throw a TypeError
  • Believes an object without __toString prints its class name
  • Thinks __toString cannot throw exceptions in PHP 8
  • Declares __invoke static to call the class like a function
  • Claims an invokable object fails a callable type check