__toStringでオブジェクトを文字列化する

__toStringは、オブジェクトを文字列の文脈に置いたときに自動で呼ばれるマジックメソッドです。echoや文字列連結、sprintfの%sでオブジェクトを文字列化できます。

オブジェクトをそのままechoしたい場面

値オブジェクトを作っていると、ログに出したり画面に表示したりするときに中身を文字列で見たくなる場面がよくあります。毎回 getValue() を呼ぶのも面倒で、そういうときに __toString を実装しておくと、オブジェクトをそのまま文字列の文脈に置けるようになりますね。echo や文字列連結、sprintf の %s に渡したときに自動で呼ばれるメソッドです。

final class Money
{
    public function __construct(
        private int $amount,
        private string $currency = 'JPY',
    ) {}

    public function __toString(): string
    {
        return number_format($this->amount) . ' ' . $this->currency;
    }
}

$price = new Money(1980);
echo $price;            // 1,980 JPY
echo "合計: {$price}"; // 合計: 1,980 JPY

返すのは必ず string でなければならず、ここで配列や null を返そうとすると致命的なエラーになります。PHP 8 以降は戻り値の型を string で明示しておくと事故が減るので、書いておくのがいいと思います。

ログ出力との相性

個人的に一番恩恵を感じるのはログですね。例外の文脈やデバッグ出力にオブジェクトを混ぜたいとき、__toString があると「人間が読める一行」を用意しておけます。ただし、ここに機密情報をそのまま出してしまうと、うっかりログへ漏れる原因になります。表示用の整形と、内部の生データは分けて考えたほうが安全だという気がします。

final class UserId
{
    public function __construct(private string $value) {}

    public function __toString(): string
    {
        return 'User#' . $this->value;
    }
}

$logger->info('ログイン: ' . new UserId('42')); // ログイン: User#42

例外を投げてはいけない

ハマりどころとして、__toString の中で例外を投げるのは避けたほうがいいです。文字列化はかなり広い場所で暗黙に呼ばれるので、そこで例外が飛ぶと原因を追いにくいエラーになりがちです。整形に失敗しそうな処理はコンストラクタ側で弾いておき、__toString はもう安全な値を組み立てるだけ、という役割分担にしておくと落ち着きます。

public function __toString(): string
{
    // ここでnew したり検証したりしない。組み立てるだけ
    return sprintf('[%s] %s', $this->level, $this->message);
}

まとめ

__toString は小さな仕組みですが、値オブジェクトの表示やログ出力の見通しをかなり良くしてくれます。戻り値は必ず文字列、中で例外を投げない、機密は載せない、この三つを意識しておけば扱いやすいと思います。表示専用の窓口だと割り切って、重い処理は持ち込まないのがコツですね。

よくある質問

Q. __toStringは何を返せばいいですか。
A. 必ずstringを返します。配列やnullを返すと致命的なエラーになるので、戻り値の型をstringで明示しておくと安全です。

Q. __toStringの中で例外を投げてもいいですか。
A. 避けたほうがいいです。文字列化は広い場所で暗黙に呼ばれるため、原因の追いにくいエラーになりがちです。

Q. いつ自動で呼ばれますか。
A. echoや文字列連結、sprintfの%sなど、オブジェクトを文字列の文脈に置いたときに呼ばれます。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です