[PHP]__toString()とは?オブジェクトを文字列として扱う方法を徹底解説

PHP

はじめに

前回までの記事で、シリアライズに関わるマジックメソッド(__sleep()、__wakeup()、__serialize()、__unserialize())を解説してきました。今回取り上げる __toString() は、オブジェクトを文字列として扱おうとした瞬間に呼び出されるマジックメソッドです。

echo $obj; や "値: {$obj}" のように、オブジェクトを文字列が期待される場所で使うと、通常は「Object of class X could not be converted to string」というエラーになります。しかし、クラスに __toString() が定義されていれば、PHPはその戻り値を文字列として使ってくれます。例外メッセージの整形、値オブジェクト(Value Object)の表示、デバッグ出力、URLやパスを表すオブジェクトの文字列化など、非常に多くの場面で活用される、実用性の高いマジックメソッドです。本記事では基本的な使い方から、PHP 8.0以降の Stringable インターフェースとの関係、注意点まで詳しく解説します。


メソッド概要

項目内容
メソッド名__toString()
種別マジックメソッド
シグネチャpublic function __toString(): string
引数なし
呼び出しタイミングオブジェクトが文字列として扱われる時(echo、文字列連結、文字列比較など)
戻り値必ず文字列(string型)
対応バージョンPHP 5.2.0以降(それ以前はechoでのみ有効)
関連インターフェースStringable(PHP 8.0以降、__toString()を持つクラスに自動的に実装される)
例外PHP 7.4以降、__toString()内から例外をスローできる

呼び出しの仕組み(イメージ図)

  $money = new Money(1500, 'JPY');

  echo $money;
  $text = "合計: " . $money;
  $text = "合計: {$money}";
  strlen($money);
  in_array('1,500 JPY', [$money]);   など
              │
              ▼
  ┌───────────────────────────┐
  │ PHPが「オブジェクトを文字列に変換する」    │
  │ 必要があると判断する                     │
  └───────────┬───────────────┘
              ▼
  ┌───────────────────────────┐
  │ __toString() が定義されているか確認       │
  └───────────┬───────────────┘
        ┌─────┴─────┐
        ▼           ▼
   定義あり        定義なし
   戻り値の文字列   Error例外:
   が使われる      "could not be converted to string"
   ★この記事の対象

ポイントは、__toString() が明示的に呼び出さなくても、文字列コンテキストで自動的に実行されるという点です。この暗黙の変換のおかげで、値オブジェクトを echo に直接渡すといった自然な書き方ができる一方、どのタイミングで呼ばれるかを意識しておかないと、予期しない副作用につながることがあります。


実践サンプル7選

例1:基本的な使い方

<?php

class User
{
    public function __construct(
        private string $name,
        private int $age
    ) {
    }

    public function __toString(): string
    {
        return "{$this->name}({$this->age}歳)";
    }
}

$user = new User('太郎', 30);
echo $user . PHP_EOL;                    // 直接echoできる
echo "ユーザー: {$user}" . PHP_EOL;       // 文字列補間でも使える
echo 'ユーザー: ' . $user . PHP_EOL;      // 文字列連結でも使える

例2:値オブジェクト(Value Object)の表示

<?php

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

    /**
     * 金額をカンマ区切りの通貨表記に整形して返す
     */
    public function __toString(): string
    {
        return number_format($this->amount) . ' ' . $this->currency;
    }
}

$price = new Money(1500000, 'JPY');
echo "価格: {$price}" . PHP_EOL; // 価格: 1,500,000 JPY

例3:Stringableインターフェースによる型宣言(PHP 8.0以降)

<?php

class Tag
{
    public function __construct(private string $label)
    {
    }

    public function __toString(): string
    {
        return "#{$this->label}";
    }
}

/**
 * PHP 8.0以降、__toString()を持つクラスは
 * 自動的にStringableインターフェースを実装したものとみなされる
 */
function printLabel(Stringable|string $label): void
{
    echo "ラベル: {$label}" . PHP_EOL;
}

printLabel('文字列そのもの');
printLabel(new Tag('PHP'));

var_dump(new Tag('test') instanceof Stringable); // true

例4:カスタム例外のメッセージ整形

<?php

class ValidationException extends Exception
{
    public function __construct(
        private array $errors,
        string $message = '入力内容に誤りがあります'
    ) {
        parent::__construct($message);
    }

    /**
     * 例外を文字列化した際の出力を、
     * エラー内容の一覧を含む形式に整形する
     */
    public function __toString(): string
    {
        $details = implode("\n", array_map(
            fn (string $field, string $msg) => "  - {$field}: {$msg}",
            array_keys($this->errors),
            $this->errors
        ));

        return static::class . ": {$this->getMessage()}\n{$details}";
    }
}

try {
    throw new ValidationException(['email' => '形式が不正です', 'age' => '数値で入力してください']);
} catch (ValidationException $e) {
    echo $e . PHP_EOL;
}

例5:URLやパスを表すオブジェクトの文字列化

<?php

class Url
{
    public function __construct(
        private string $scheme,
        private string $host,
        private string $path = '/',
        private array $query = []
    ) {
    }

    public function __toString(): string
    {
        $url = "{$this->scheme}://{$this->host}{$this->path}";

        if (!empty($this->query)) {
            $url .= '?' . http_build_query($this->query);
        }

        return $url;
    }

    public function withQuery(array $query): self
    {
        return new self($this->scheme, $this->host, $this->path, $query);
    }
}

$url = new Url('https', 'example.com', '/search');
$withQuery = $url->withQuery(['q' => 'PHP', 'page' => 2]);
echo $url . PHP_EOL;        // https://example.com/search
echo $withQuery . PHP_EOL;  // https://example.com/search?q=PHP&page=2

例6:デバッグ用に内部状態を分かりやすく出力するクラス

<?php

class Order
{
    private array $items = [];

    public function __construct(private string $orderId)
    {
    }

    public function addItem(string $name, int $quantity): void
    {
        $this->items[$name] = $quantity;
    }

    /**
     * デバッグ時にvar_dump()ではなく、
     * 人間が読みやすい形式で内容を確認できるようにする
     */
    public function __toString(): string
    {
        $lines = ["注文ID: {$this->orderId}"];
        foreach ($this->items as $name => $quantity) {
            $lines[] = "  - {$name} × {$quantity}";
        }

        return implode("\n", $lines);
    }
}

$order = new Order('ORD-001');
$order->addItem('ノート', 3);
$order->addItem('ペン', 5);
echo $order . PHP_EOL;

例7:文字列型引数を受け取る関数にオブジェクトを渡す

<?php

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

    public function __toString(): string
    {
        return strtolower(preg_replace('/[^a-zA-Z0-9]+/', '-', $this->value));
    }
}

$slug = new Slug('Hello World PHP');

// strlen()やstr_contains()など、文字列を受け取る組み込み関数に渡すと
// 暗黙的に__toString()が呼ばれる(strict_typesが無効な場合)
echo strlen((string) $slug) . PHP_EOL;       // 明示的なキャストが安全
echo str_contains((string) $slug, 'php') ? '含まれています' : '含まれていません';
echo PHP_EOL;

関連機能との比較

機能役割__toString()との違い
__toString()オブジェクトが文字列として扱われる際の変換を定義本記事の対象。暗黙的に自動呼び出しされる
Stringable インターフェース__toString()を持つことを型として表現PHP 8.0以降、__toString()を持つクラスに自動実装される
(string) キャスト明示的に文字列へ変換する__toString()が定義されていればそれを呼び出す、明示的な変換構文
JsonSerializablejson_encode()時の出力を制御する文字列化ではなくJSON化の挙動を制御する別の仕組み
__debugInfo()var_dump()時の出力内容を制御する文字列変換ではなく、デバッグ表示専用の仕組み

よくある落とし穴(注意点)

  1. 戻り値は必ず文字列でなければならない __toString() が文字列以外(null や整数など)を返すと、エラーが発生します。数値を返したい場合でも、必ず (string) キャストや文字列補間を通して文字列にしてから返しましょう。
  2. __toString()内で重い処理や副作用のある処理を行わない __toString() は文字列コンテキストで暗黙的に呼ばれるため、「いつ・何回呼ばれるか」が見えにくいメソッドです。データベースアクセスなどの重い処理や、状態を変更するような副作用のある処理をここに入れると、予期しないタイミングで実行されてバグの原因になります。
  3. PHP 7.4以降は例外をスローできるが、使いどころを限定する PHP 7.4より前は、__toString() 内で例外をスローすること自体が禁止されていました。PHP 7.4以降はスローできるようになりましたが、echo などの何気ない呼び出しで予期せず例外が発生すると、デバッグが困難になります。エラーになりうる処理は避け、安全な文字列を返す設計が望ましいです。
  4. 意図しない暗黙の文字列変換が起きる in_array() や == による比較、array_unique() などの関数で、オブジェクトが意図せず文字列に変換されて比較されることがあります。厳密な比較が必要な場合は === や、明示的なメソッドを使いましょう。
  5. Stringableの自動実装を過信しない PHP 8.0以降、__toString() を定義したクラスは自動的に Stringable を実装したとみなされますが、それより古いバージョンでは自動実装されません。古いPHPバージョンもサポートする必要がある場合は、implements Stringable を明示的に宣言しておくと安全です(ただし Stringable 自体はPHP 8.0以降でのみ存在します)。

まとめ

観点まとめ
何をするメソッドかオブジェクトが文字列として扱われる際に、その文字列表現を返す
主な用途値オブジェクトの表示、例外メッセージの整形、URLやパスの文字列化、デバッグ出力
戻り値の制約必ずstring型を返す必要がある
関連する仕組みPHP 8.0以降はStringableインターフェースが自動実装される
注意点重い処理や副作用を避けること、例外スローの慎重な扱い、暗黙の変換による予期しない挙動

__toString() は、オブジェクトを自然に文字列として扱うための、日常的に使用頻度の高いマジックメソッドです。値オブジェクトの設計や、例外・ログの整形など、幅広い場面で活用できます。ただし、暗黙的に呼ばれるという特性上、シンプルで副作用のない実装を心がけることが、安全で保守しやすいコードへの近道です。

タイトルとURLをコピーしました