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?
answer
- added in PHP 8.0
- one method: __toString(): string
- implicit when __toString is declared
- strict_types rejects objects for string
- instanceof Stringable
basics
~20 sStringable, 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
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_typesgo deeper
Recall that Stringable is the PHP 8 interface for objects with __toString(), and that declaring __toString() is enough to implement it.
Explain why string|Stringable exists: strict_types rejects objects for string parameters, while coercive mode converts them immediately.
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.
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