skip to content

In PHP 8, what is the Stringable interface, when does a class implement it without declaring it, and why type a parameter as string|Stringable?

level: middleimportance: nice to knowfreq 25%

answer

  1. added in PHP 8.0
  2. one method: __toString(): string
  3. implicit when __toString is declared
  4. strict_types rejects objects for string
  5. instanceof Stringable

basics

~20 s

Stringable, added in PHP 8.0, is the interface of objects with __toString(). A class that declares __toString() implements it automatically. Typing string|Stringable accepts strings and such objects even under strict_types, where a plain string type rejects objects.

solid answer

~30 s

`Stringable` declares a single method, `__toString(): string`. Since **PHP 8.0** any class that declares `__toString()` implements `Stringable` automatically, so `$code instanceof Stringable` is true even without `implements Stringable`; the manual still recommends writing it explicitly for readers. Its main use is the union type `string|Stringable`. With `declare(strict_types=1)`, a `string` parameter accepts only real strings, so passing a `TicketCode` object throws a `TypeError`; `string|Stringable` accepts both, and the function converts with `(string) $value` when, and if, it needs the text. In coercive mode a `string` parameter would convert the object immediately instead, losing the object.

code

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

final class TicketCode
{
    public function __construct(private string $event, private int $number) {}

    public function __toString(): string
    {
        return sprintf('%s-%05d', $this->event, $this->number);
    }
}

function printCode(string|Stringable $code): void
{
    echo 'Ticket ', (string) $code, "\n";
}

function printStrict(string $code): void
{
    echo 'Ticket ', $code, "\n";
}

$code = new TicketCode('EVT', 42);
var_dump($code instanceof Stringable); // bool(true), no implements needed
printCode($code);                    // Ticket EVT-00042
printCode('EVT-00043');              // Ticket EVT-00043
printStrict($code);                  // TypeError under strict_types

go deeper

for a junior

Recall that Stringable is the PHP 8 interface for objects with __toString(), and that declaring __toString() is enough to implement it.

for a middle

Explain why string|Stringable exists: strict_types rejects objects for string parameters, while coercive mode converts them immediately.

for a senior

Use string|Stringable to accept value objects at API boundaries and defer conversion, and know when a narrower interface should replace it because the format matters.

for a principal

Decide how a codebase exposes value objects at string-typed boundaries, balancing strict typing, explicit conversion and the risk of format drift between implementers.

## The interface itself `Stringable` is a built-in interface, added in **PHP 8.0**, with one method: ```php interface Stringable { public function __toString(): string; } ``` It names a **capability**: this object can be turned into a string. What `__toString()` does and its own rules belong to magic methods; this interface is about typing against that capability. ## Implemented automatically Unlike most interfaces, `Stringable` is added **implicitly**. When a class declares `__toString()`, the engine adds `Stringable` to its interface list, so: - `$code instanceof Stringable` is true for any object whose class declares `__toString()`. - The class passes `Stringable` type checks without writing `implements Stringable`. - Writing `implements Stringable` explicitly is still allowed and recommended by the manual, because it tells the reader the string form is a deliberate part of the class's contract. The automatic behaviour exists so that the many classes written before PHP 8.0 with a `__toString()` method satisfy the new type without edits. ## Why string|Stringable matters The union type answers a problem with **strict typing**. In a file with `declare(strict_types=1)`, a scalar type declaration accepts only a value of exactly that type (with the single exception of `int` passed to `float`). An object is not a `string`, so: | Parameter type | Caller file strict_types | Passing a `TicketCode` object | |---|---|---| | `string` | off (coercive, the default) | converted immediately through `__toString()` | | `string` | on | `TypeError` | | `string\|Stringable` | either | accepted as the object | | `Stringable` | either | accepted; plain strings rejected | With `string|Stringable`, a logging or templating function can accept either a literal string or a value object and call `(string) $value` only when it actually needs the text. That can save work: a message object whose string form is expensive to build is converted only if the log level is enabled. ## Using it in a ticketing site A `TicketCode` value object holds an event prefix and a number and renders as `EVT-00042`. Functions that print, log or embed ticket codes type their parameter as `string|Stringable`: 1. Callers pass either a raw string from a form or the `TicketCode` object. 2. Strict-typed callers get no `TypeError`. 3. The function converts once, with `(string) $code`, at the point of output. ## A checklist for a Stringable value object - Declare `__toString(): string` and, for readers, also write `implements Stringable`. - Keep the string form **cheap and side-effect free**; it may run inside logging, templates or exception messages. - Make the string form a stable, documented format when other code parses or stores it, such as ticket codes printed on a PDF and later scanned back. - Accept `string|Stringable` at boundaries that only need text, and convert once with `(string)` where the text is used. - Keep the object, not its string, wherever domain rules still apply; a `TicketCode` can validate its own event prefix, a string cannot. ## Limits and pitfalls - `Stringable` says nothing about the **format** of the string; two `Stringable` classes can produce completely different text. If callers depend on a format, give them a more specific interface. - A parameter typed only `Stringable` rejects plain strings, which is rarely what an API wants. - Under coercive typing, relying on implicit conversion to `string` hides the conversion; `string|Stringable` makes it visible and deferrable. - Strict typing depends on the **calling** file's `declare(strict_types=1)`, not the file where the function is defined.

  • Without strict_types, would printStrict($code) work?
    Yes. In coercive mode a `string` parameter accepts an object with `__toString()` and converts it on the way in, so `$code` inside the function is already a string. The object is lost at the boundary, which is exactly what `string|Stringable` avoids when a function wants to keep it or defer the conversion.
  • Why does the manual recommend writing implements Stringable when PHP adds it anyway?
    The explicit declaration documents intent: the string form is a supported part of the class's contract, not an incidental debugging helper. It also keeps the class declaration honest for readers and tools that look only at the `implements` list.

saying these in an interview costs you the question

  • A class must write implements Stringable to pass a Stringable type check
  • Under strict_types, a string parameter accepts any object with __toString()
  • Stringable was added in PHP 7.4 alongside typed properties
  • A Stringable parameter also accepts plain string values
  • Stringable guarantees a particular string format across implementers