__toString cannot throw before 7.4, and then it can

Throwing from __toString was a fatal error, which meant a value object that could not render itself had to return a placeholder string and hope somebody noticed.

final class Money
{
    public function __toString(): string
    {
        if ($this->currency === null) {
            // 7.3: fatal. 7.4: a normal exception.
            throw new LogicException('no currency');
        }

        return sprintf('%s %.2f', $this->currency, $this->cents / 100);
    }
}

7.4 lifts the restriction, and the reason it existed is worth understanding before relying on it: string conversion happens in places with no error handling around them, including inside the engine’s own error message construction. An exception thrown while building an exception message is not a good afternoon. The safer position remains that __toString should be total — if a value can fail to render, an explicit method that can throw is clearer than a magic one that might.