PHPのマジックメソッドとはなんなのか

採用はこちら

PHPのマジックメソッドとは、オブジェクトに対して特定の操作が行われたときに、PHPが自動的に呼び出す特別なメソッドのことです。

たとえば、クラスからオブジェクトを生成したときに自動で呼ばれる __construct()、存在しないプロパティにアクセスしたときに呼ばれる __get()、存在しないメソッドを呼び出したときに呼ばれる __call() などがあります。

通常のメソッドは、開発者が明示的に呼び出します。

$user->getName();

一方、マジックメソッドは、PHP側が決められたタイミングで自動的に呼び出します。

$user = new User();

このとき、User クラスに __construct() が定義されていれば、new User() のタイミングで自動的に __construct() が実行されます。

マジックメソッドは、名前が必ずダブルアンダースコア __ から始まるのが特徴です。

__construct()
__get()
__set()
__call()
__toString()

ただし、__ から始まるメソッド名はPHPが予約しているため、PHPで定義されていない独自のマジックメソッド名を作ることは避けるべきです。

たとえば、次のような命名はおすすめできません。

public function __myOriginalMethod()
{
    // 独自の用途で __ から始まるメソッドを作るのは避ける
}

将来のPHPで同じ名前のマジックメソッドが追加された場合、予期しない衝突が起きる可能性があるためです。

目次

マジックメソッドの基本的な考え方

通常のメソッドとの違い

通常のメソッドは、自分で呼び出したときに実行されます。

class User
{
    public function greet(): void
    {
        echo 'こんにちは';
    }
}

$user = new User();
$user->greet();

この場合、greet()$user->greet() と書いたタイミングで実行されます。

一方、マジックメソッドは、PHPが特定の操作を検知したときに自動で呼び出します。

class User
{
    public function __construct()
    {
        echo 'オブジェクトが作成されました';
    }
}

$user = new User();

この例では、__construct() を直接呼び出していません。

しかし、new User() によってオブジェクトを生成したタイミングで、PHPが自動的に __construct() を実行します。

つまり、マジックメソッドは「特定のイベントに反応して自動実行されるメソッド」と考えると理解しやすいです。

マジックメソッドを使う目的

マジックメソッドは、PHPのオブジェクト操作に対する動作をカスタマイズするために使います。

たとえば、次のような目的があります。

目的使用する主なマジックメソッド
オブジェクト生成時に初期化したい__construct()
オブジェクト破棄時に後処理したい__destruct()
存在しないプロパティを動的に扱いたい__get() / __set()
存在しないメソッド呼び出しを処理したい__call() / __callStatic()
オブジェクトを文字列として扱いたい__toString()
オブジェクトを関数のように呼び出したい__invoke()
オブジェクト複製時の処理を制御したい__clone()
シリアライズ処理を制御したい__serialize() / __unserialize()

マジックメソッドを使うと、柔軟で直感的なコードを書けます。

ただし、処理が暗黙的に実行されるため、使いすぎるとコードの流れが分かりにくくなります。

そのため、実務では「本当に必要な場面だけ使う」ことが重要です。

代表的なマジックメソッド一覧

よく使われるマジックメソッド

PHPには複数のマジックメソッドがあります。

代表的なものを一覧にすると、次のようになります。

メソッド呼ばれるタイミング
__construct()オブジェクト生成時
__destruct()オブジェクト破棄時
__get()存在しない、またはアクセスできないプロパティを読み取る時
__set()存在しない、またはアクセスできないプロパティへ代入する時
__isset()isset() / empty() をアクセスできないプロパティに対して使った時
__unset()unset() をアクセスできないプロパティに対して使った時
__call()存在しない、またはアクセスできないインスタンスメソッドを呼んだ時
__callStatic()存在しない、またはアクセスできない静的メソッドを呼んだ時
__toString()オブジェクトを文字列として扱った時
__invoke()オブジェクトを関数のように呼び出した時
__clone()clone でオブジェクトを複製した時
__debugInfo()var_dump() した時
__serialize()serialize() した時
__unserialize()unserialize() した時
__sleep()serialize() した時に呼ばれる古い方式
__wakeup()unserialize() した時に呼ばれる古い方式
__set_state()var_export() の出力からオブジェクトを復元する時

初心者のうちは、すべてを一度に覚える必要はありません。

まずは、__construct()__get()__set()__call()__toString() あたりから理解するとよいでしょう。

マジックメソッドの注意点

マジックメソッドを定義するときは、PHPで決められたシグネチャに合わせる必要があります。

たとえば、__get() は次のように定義します。

public function __get(string $name): mixed
{
    // 処理
}

__set() は次のように定義します。

public function __set(string $name, mixed $value): void
{
    // 処理
}

また、__construct()__destruct() には戻り値の型を付けてはいけません。

次のような書き方は避けます。

class User
{
    public function __construct(): void
    {
        // NG
    }

    public function __destruct(): void
    {
        // NG
    }
}

__construct()__destruct() は、次のように戻り値型を付けずに定義します。

class User
{
    public function __construct()
    {
        // OK
    }

    public function __destruct()
    {
        // OK
    }
}

また、__construct()__destruct()__clone() を除くマジックメソッドは、基本的に public で定義する必要があります。

__construct():オブジェクト生成時に呼ばれるメソッド

__construct() の基本

__construct() は、クラスからオブジェクトを生成したときに自動的に呼ばれるマジックメソッドです。

一般的には、コンストラクタと呼ばれます。

class User
{
    public string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

$user = new User('田中');

echo $user->name;

実行結果は次のようになります。

田中

new User('田中') と書いた時点で、PHPが自動的に __construct() を呼び出します。

その結果、$name'田中' が渡され、$this->name に値が設定されます。

__construct() の主な使い方

__construct() は、オブジェクトの初期化に使います。

たとえば、記事クラスを作る場合は、次のように書けます。

class Article
{
    public string $title;
    public string $body;
    public bool $published;

    public function __construct(string $title, string $body)
    {
        $this->title = $title;
        $this->body = $body;
        $this->published = false;
    }
}

$article = new Article('PHP入門', '本文です');

この例では、記事タイトルと本文を必須で受け取り、公開状態は初期値として false にしています。

__construct() を使うと、オブジェクト作成時に必要な値を強制できるため、不完全な状態のオブジェクトを作りにくくなります。

コンストラクタプロパティ昇格を使った書き方

PHP 8以降では、コンストラクタプロパティ昇格を使って、より短く書くこともできます。

class User
{
    public function __construct(
        public string $name,
        public string $email
    ) {}
}

$user = new User('田中', 'tanaka@example.com');

echo $user->name;

この書き方では、コンストラクタの引数とプロパティ定義をまとめて書けます。

実務では、DTOや値オブジェクトなどでよく使われる書き方です。

__destruct():オブジェクト破棄時に呼ばれるメソッド

__destruct() の基本

__destruct() は、オブジェクトが破棄されるときに自動的に呼ばれるマジックメソッドです。

一般的には、デストラクタと呼ばれます。

class Logger
{
    public function __construct()
    {
        echo '開始しました' . PHP_EOL;
    }

    public function __destruct()
    {
        echo '終了しました' . PHP_EOL;
    }
}

$logger = new Logger();

echo '処理中です' . PHP_EOL;

実行結果は次のようになります。

開始しました
処理中です
終了しました

スクリプトの終了時などにオブジェクトが破棄され、__destruct() が自動的に呼ばれます。

__destruct() の使いどころ

__destruct() は、後片付け処理に使われることがあります。

たとえば、ファイルを開いたあとに閉じる処理です。

class FileWriter
{
    private $handle;

    public function __construct(string $filePath)
    {
        $this->handle = fopen($filePath, 'a');
    }

    public function write(string $text): void
    {
        fwrite($this->handle, $text);
    }

    public function __destruct()
    {
        fclose($this->handle);
    }
}

ただし、実務では __destruct() に重要な処理を詰め込みすぎないほうが安全です。

__destruct() は呼ばれるタイミングがコード上から分かりにくいため、処理の見通しが悪くなることがあります。

特に、データベース更新、外部API通信、重要なログ送信などを __destruct() に依存させると、デバッグしづらくなる場合があります。

__get():プロパティ読み取り時に呼ばれるメソッド

__get() の基本

__get() は、存在しないプロパティ、または外部からアクセスできないプロパティを読み取ろうとしたときに呼ばれます。

class User
{
    private array $data = [
        'name' => '佐藤',
        'email' => 'sato@example.com',
    ];

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}

$user = new User();

echo $user->name;

実行結果は次のようになります。

佐藤

User クラスには、public $name というプロパティはありません。

しかし、$user->name とアクセスしたときに、PHPが自動的に __get('name') を呼び出します。

その結果、$this->data['name'] の値が返されます。

__get() が便利な場面

__get() は、内部では配列などでデータを管理しながら、外部からはプロパティのようにアクセスさせたい場合に使われます。

たとえば、次のような書き方です。

$user->name;
$user->email;
$user->created_at;

このようなコードは、通常のプロパティアクセスに見えます。

しかし内部では、__get() を通じてデータを取得している場合があります。

LaravelのEloquentモデルなどでも、マジックメソッドや属性アクセスの仕組みによって、カラム値やリレーションを通常のプロパティのように扱える場面があります。

__get() の注意点

__get() は便利ですが、存在しないプロパティ名でも処理できてしまうため、タイポに気づきにくくなることがあります。

たとえば、次のようなコードです。

echo $user->emial;

本当は email と書きたかったのに、emial と間違えているケースです。

__get() の中で単純に null を返す実装にしていると、このようなミスに気づきにくくなります。

public function __get(string $name): mixed
{
    return $this->data[$name] ?? null;
}

実務では、存在しないプロパティにアクセスされた場合は、例外を投げる設計にしたほうが安全な場合があります。

class User
{
    private array $data = [
        'name' => '佐藤',
        'email' => 'sato@example.com',
    ];

    public function __get(string $name): mixed
    {
        if (!array_key_exists($name, $this->data)) {
            throw new InvalidArgumentException("{$name} は存在しません");
        }

        return $this->data[$name];
    }
}

このようにすると、タイポや不正なプロパティアクセスに早く気づけます。

__set():プロパティ代入時に呼ばれるメソッド

__set() の基本

__set() は、存在しないプロパティ、または外部からアクセスできないプロパティに値を代入しようとしたときに呼ばれます。

class User
{
    private array $data = [];

    public function __set(string $name, mixed $value): void
    {
        $this->data[$name] = $value;
    }

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}

$user = new User();

$user->name = '鈴木';

echo $user->name;

実行結果は次のようになります。

鈴木

$user->name = '鈴木'; のタイミングで、PHPが __set('name', '鈴木') を呼び出します。

その後、$user->name を読み取ると、__get('name') が呼ばれます。

__set() の安全な書き方

__set() を使う場合は、どのプロパティ名を許可するか決めておくと安全です。

class User
{
    private array $data = [];
    private array $allowed = ['name', 'email'];

    public function __set(string $name, mixed $value): void
    {
        if (!in_array($name, $this->allowed, true)) {
            throw new InvalidArgumentException("{$name} は設定できません");
        }

        $this->data[$name] = $value;
    }

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}

このようにすると、許可されていないプロパティ名への代入を防げます。

たとえば、次のようなタイポがあった場合です。

$user->emial = 'test@example.com';

email と書くべきところを emial と間違えています。

許可リストを用意しておけば、emial は許可されていないため、例外を発生させることができます。

__set() の戻り値

__set() の戻り値は、代入処理では利用されません。

そのため、基本的には void で定義します。

public function __set(string $name, mixed $value): void
{
    $this->data[$name] = $value;
}

何か値を返すように書いても、通常の代入処理ではその戻り値は使われません。

__isset():issetやemptyを使ったときに呼ばれるメソッド

__isset() の基本

__isset() は、アクセスできないプロパティに対して isset() または empty() を使ったときに呼ばれます。

class User
{
    private array $data = [
        'name' => '田中',
    ];

    public function __isset(string $name): bool
    {
        return isset($this->data[$name]);
    }
}

$user = new User();

var_dump(isset($user->name));
var_dump(isset($user->email));

実行結果は次のようになります。

bool(true)
bool(false)

$user->namepublic プロパティとしては存在しません。

しかし、isset($user->name) と書いたことで、PHPが __isset('name') を呼び出しています。

__isset() の使いどころ

__get() で動的なプロパティアクセスを実装している場合、__isset() もあわせて実装すると自然です。

class User
{
    private array $data = [
        'name' => '田中',
        'email' => null,
    ];

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }

    public function __isset(string $name): bool
    {
        return isset($this->data[$name]);
    }
}

このようにすると、動的に扱っているプロパティに対して isset() を使ったときの動作も制御できます。

__unset():unsetを使ったときに呼ばれるメソッド

__unset() の基本

__unset() は、アクセスできないプロパティに対して unset() を使ったときに呼ばれます。

class User
{
    private array $data = [
        'name' => '田中',
    ];

    public function __unset(string $name): void
    {
        unset($this->data[$name]);
    }

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}

$user = new User();

unset($user->name);

var_dump($user->name);

実行結果は次のようになります。

NULL

unset($user->name) のタイミングで、PHPが __unset('name') を呼び出します。

その結果、内部配列の name が削除されます。

__unset() を使うときの注意点

__unset() も、__get()__set() と同じく、動作が見えにくくなりやすいメソッドです。

そのため、削除を許可するプロパティと許可しないプロパティを明確にしておくと安全です。

class User
{
    private array $data = [
        'name' => '田中',
        'email' => 'tanaka@example.com',
    ];

    private array $unsettable = ['email'];

    public function __unset(string $name): void
    {
        if (!in_array($name, $this->unsettable, true)) {
            throw new InvalidArgumentException("{$name} は削除できません");
        }

        unset($this->data[$name]);
    }
}

この例では、email は削除できますが、name は削除できません。

__call():存在しないメソッドを呼んだときに呼ばれるメソッド

__call() の基本

__call() は、存在しないインスタンスメソッド、または外部からアクセスできないインスタンスメソッドを呼び出したときに呼ばれます。

class User
{
    public function __call(string $name, array $arguments): mixed
    {
        echo "{$name} メソッドは存在しません";

        return null;
    }
}

$user = new User();

$user->sayHello();

実行結果は次のようになります。

sayHello メソッドは存在しません

User クラスには sayHello() というメソッドがありません。

そのため、PHPが自動的に __call('sayHello', []) を呼び出します。

__call() の実用例

__call() は、メソッド名を動的に解釈したいときに使われます。

たとえば、getName()getEmail() のようなメソッドを動的に処理する簡単な例です。

class User
{
    private array $data = [
        'name' => '田中',
        'email' => 'tanaka@example.com',
    ];

    public function __call(string $name, array $arguments): mixed
    {
        if (str_starts_with($name, 'get')) {
            $property = lcfirst(substr($name, 3));

            if (!array_key_exists($property, $this->data)) {
                throw new BadMethodCallException("{$name} は存在しません");
            }

            return $this->data[$property];
        }

        throw new BadMethodCallException("{$name} は存在しません");
    }
}

$user = new User();

echo $user->getName();

実行結果は次のようになります。

田中

getName() というメソッドは、実際には定義されていません。

しかし、__call() の中で get から始まるメソッド名を解釈し、name の値を返しています。

__call() の注意点

__call() は便利ですが、使いすぎるとコードの見通しが悪くなります。

たとえば、次のコードを見た人は、getName() が実際に定義されているメソッドなのか、__call() によって動的に処理されているのか分かりにくくなります。

$user->getName();

また、IDEの補完や静的解析ツールが正しく認識できない場合もあります。

そのため、__call() を使う場合は、PHPDocで補足するとよいです。

/**
 * @method string getName()
 * @method string getEmail()
 */
class User
{
    // ...
}

__callStatic():存在しない静的メソッドを呼んだときに呼ばれるメソッド

__callStatic() の基本

__callStatic() は、存在しない静的メソッド、または外部からアクセスできない静的メソッドを呼び出したときに呼ばれます。

class Helper
{
    public static function __callStatic(string $name, array $arguments): mixed
    {
        echo "{$name} という静的メソッドは存在しません";

        return null;
    }
}

Helper::formatText();

実行結果は次のようになります。

formatText という静的メソッドは存在しません

Helper::formatText() という静的メソッドは定義されていません。

そのため、PHPが __callStatic('formatText', []) を呼び出します。

__callStatic() の使いどころ

__callStatic() は、静的メソッド呼び出しを動的に処理したい場合に使われます。

ただし、静的メソッドの動的処理は、処理の追跡が難しくなりやすいため、実務では慎重に使うべきです。

明示的なメソッドを定義したほうが分かりやすい場合も多くあります。

class TextHelper
{
    public static function formatText(string $text): string
    {
        return trim($text);
    }
}

単純な処理であれば、このように明示的なメソッドを定義したほうが読みやすくなります。

__toString():オブジェクトを文字列として扱うメソッド

__toString() の基本

__toString() は、オブジェクトを文字列として扱おうとしたときに呼ばれるマジックメソッドです。

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

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

$user = new User('山田');

echo $user;

実行結果は次のようになります。

山田

通常、オブジェクトをそのまま echo することはできません。

しかし、__toString() を定義しておくと、PHPがオブジェクトを文字列として扱う場面で __toString() を呼び出します。

__toString() の実用例

__toString() は、値オブジェクトやログ出力などで便利です。

たとえば、メールアドレスを表すクラスを作る場合です。

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

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

$email = new Email('test@example.com');

echo $email;

実行結果は次のようになります。

test@example.com

商品情報を文字列で表現することもできます。

class Product
{
    public function __construct(
        private string $name,
        private int $price
    ) {}

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

$product = new Product('コーヒー', 500);

echo $product;

実行結果は次のようになります。

コーヒー:500円

__toString() の注意点

__toString() は、実務上は必ず文字列を返すように実装するのが安全です。

public function __toString(): string
{
    return '文字列';
}

PHP 8.0以降では、__toString() を持つクラスは暗黙的に Stringable として扱われます。

ただし、型変換の挙動に依存すると分かりにくくなるため、基本的には戻り値の型を string と明示し、確実に文字列を返すように書くのがおすすめです。

__invoke():オブジェクトを関数のように呼び出すメソッド

__invoke() の基本

__invoke() は、オブジェクトを関数のように呼び出したときに実行されます。

class TaxCalculator
{
    public function __invoke(int $price): int
    {
        return (int) ($price * 1.1);
    }
}

$taxCalculator = new TaxCalculator();

echo $taxCalculator(1000);

実行結果は次のようになります。

1100

$taxCalculator はオブジェクトです。

しかし、$taxCalculator(1000) のように関数のような形で呼び出しています。

このとき、PHPが自動的に __invoke(1000) を実行します。

__invoke() の実用例

__invoke() は、処理そのものをオブジェクトとして表現したいときに便利です。

たとえば、バリデーションルールをクラス化できます。

class Required
{
    public function __invoke(string $value): bool
    {
        return trim($value) !== '';
    }
}

$required = new Required();

var_dump($required('テスト'));
var_dump($required(''));

実行結果は次のようになります。

bool(true)
bool(false)

このようにすると、「必須チェック」という処理を関数のように使えるオブジェクトとして扱えます。

また、アクションクラスのような設計にも向いています。

class SendWelcomeMail
{
    public function __invoke(User $user): void
    {
        // メール送信処理
    }
}

__invoke() を使うと、クラスの役割が「ひとつの処理」であることを表現しやすくなります。

__clone():オブジェクト複製時に呼ばれるメソッド

__clone() の基本

__clone() は、clone キーワードでオブジェクトを複製したときに呼ばれます。

class User
{
    public function __construct(
        public string $name
    ) {}

    public function __clone()
    {
        $this->name = $this->name . ' のコピー';
    }
}

$user1 = new User('田中');

$user2 = clone $user1;

echo $user2->name;

実行結果は次のようになります。

田中 のコピー

clone $user1 によってオブジェクトが複製されたあと、__clone() が呼ばれます。

浅いコピーと深いコピー

PHPの clone は、基本的に浅いコピーです。

つまり、オブジェクトの中に別のオブジェクトを持っている場合、その内側のオブジェクトまでは自動的に複製されません。

class Profile
{
    public function __construct(
        public string $bio
    ) {}
}

class User
{
    public function __construct(
        public string $name,
        public Profile $profile
    ) {}
}

$profile = new Profile('こんにちは');
$user1 = new User('田中', $profile);

$user2 = clone $user1;

$user2->profile->bio = '変更しました';

echo $user1->profile->bio;

実行結果は次のようになります。

変更しました

$user2profile を変更したにもかかわらず、$user1profile も変わっています。

これは、$user1->profile$user2->profile が同じ Profile オブジェクトを参照しているためです。

この問題を避けたい場合は、__clone() の中で内側のオブジェクトも明示的に複製します。

class User
{
    public function __construct(
        public string $name,
        public Profile $profile
    ) {}

    public function __clone()
    {
        $this->profile = clone $this->profile;
    }
}

このようにすると、User を複製したときに、内部の Profile オブジェクトも別物として複製できます。

__debugInfo():var_dumpの表示内容を制御するメソッド

__debugInfo() の基本

__debugInfo() は、var_dump() したときに表示する情報を制御できるマジックメソッドです。

class User
{
    private string $password = 'secret-password';

    public function __construct(
        private string $name
    ) {}

    public function __debugInfo(): array
    {
        return [
            'name' => $this->name,
            'password' => '********',
        ];
    }
}

$user = new User('田中');

var_dump($user);

出力例は次のようになります。

object(User)#1 (2) {
  ["name"]=>
  string(6) "田中"
  ["password"]=>
  string(8) "********"
}

本来であれば、password の実値が表示される可能性があります。

しかし、__debugInfo() を定義することで、デバッグ表示用に値をマスクできます。

__debugInfo() の使いどころ

__debugInfo() は、デバッグ時に見せたくない情報を隠したい場合に便利です。

たとえば、次のような情報はそのまま表示しないほうが安全です。

情報理由
パスワード漏えいリスクが高い
APIキー外部サービスへの不正利用につながる
トークン認証情報として悪用される可能性がある
個人情報プライバシー保護が必要

ただし、__debugInfo() はあくまで var_dump() の表示制御です。

セキュリティ対策として過信せず、機密情報そのものの扱いにも注意が必要です。

__serialize():シリアライズ時に呼ばれるメソッド

__serialize() の基本

__serialize() は、オブジェクトを serialize() するときに呼ばれるマジックメソッドです。

class User
{
    private string $name;
    private string $email;
    private string $password;

    public function __construct(string $name, string $email, string $password)
    {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
    }

    public function __serialize(): array
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
        ];
    }
}

$user = new User('田中', 'tanaka@example.com', 'secret');

echo serialize($user);

__serialize() では、シリアライズ対象にしたいデータを配列で返します。

この例では、nameemail だけをシリアライズし、password は含めていません。

__serialize() の使いどころ

__serialize() は、オブジェクトを保存したり、キャッシュしたり、セッションに保持したりするときに使われることがあります。

ただし、何をシリアライズ対象にするかは慎重に決める必要があります。

たとえば、パスワード、アクセストークン、APIキーなどの機密情報は、安易にシリアライズしないほうが安全です。

public function __serialize(): array
{
    return [
        'name' => $this->name,
        'email' => $this->email,
        // password は含めない
    ];
}

このように、保存すべき情報だけを明示的に返す設計にすると安全です。

__unserialize():復元時に呼ばれるメソッド

__unserialize() の基本

__unserialize() は、unserialize() によってオブジェクトが復元されるときに呼ばれるマジックメソッドです。

class User
{
    private string $name;
    private string $email;
    private string $password;

    public function __serialize(): array
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
        ];
    }

    public function __unserialize(array $data): void
    {
        $this->name = $data['name'];
        $this->email = $data['email'];
        $this->password = '';
    }
}

__unserialize() は、__serialize() で返した配列を受け取り、オブジェクトの状態を復元します。

この例では、password はシリアライズ対象にしていないため、復元時には空文字を設定しています。

__unserialize() の注意点

unserialize() は、外部から受け取った信頼できないデータに対して使うべきではありません。

悪意のあるシリアライズ済みデータを unserialize() すると、意図しないオブジェクトが復元され、マジックメソッドが呼び出される可能性があります。

特に、ユーザー入力、外部API、Cookie、POSTデータなどをそのまま unserialize() するのは危険です。

$user = unserialize($_POST['data']); // 危険

外部データを扱う場合は、JSONなど別の形式を使うほうが安全です。

$data = json_decode($_POST['data'], true);

シリアライズは便利ですが、セキュリティ上のリスクもあるため、使う場面を慎重に選ぶ必要があります。

__sleep()__wakeup():古いシリアライズ制御のメソッド

__sleep() の基本

__sleep() は、オブジェクトを serialize() するときに呼ばれる古い方式のマジックメソッドです。

class User
{
    public string $name;
    public string $email;
    public string $password;

    public function __construct()
    {
        $this->name = '田中';
        $this->email = 'tanaka@example.com';
        $this->password = 'secret';
    }

    public function __sleep(): array
    {
        return ['name', 'email'];
    }
}

$user = new User();

echo serialize($user);

__sleep() では、シリアライズ対象にするプロパティ名を配列で返します。

この例では、password はシリアライズ対象から外しています。

__wakeup() の基本

__wakeup() は、unserialize() でオブジェクトが復元されるときに呼ばれる古い方式のマジックメソッドです。

class User
{
    public string $name;
    public string $createdAt;

    public function __wakeup(): void
    {
        $this->createdAt = date('Y-m-d H:i:s');
    }
}

__wakeup() は、復元時に再初期化したい値がある場合に使われます。

たとえば、データベース接続やファイルハンドルなど、シリアライズできないリソースを復元時に再接続するような用途です。

新しいコードでは __serialize()__unserialize() を使う

__sleep()__wakeup() は、現在でも利用できます。

ただし、新しくコードを書く場合は、__serialize()__unserialize() を使うほうが推奨されます。

理由は、__serialize()__unserialize() のほうが柔軟で扱いやすく、シリアライズするデータを連想配列として明示できるためです。

public function __serialize(): array
{
    return [
        'name' => $this->name,
        'email' => $this->email,
    ];
}

public function __unserialize(array $data): void
{
    $this->name = $data['name'];
    $this->email = $data['email'];
}

また、同じクラスに __serialize()__sleep() の両方が定義されている場合は、__serialize() が優先されます。

同様に、__unserialize()__wakeup() の両方が定義されている場合は、__unserialize() が優先されます。

そのため、古いコードを読むときは __sleep() / __wakeup() を理解しておく必要がありますが、新しく実装するなら __serialize() / __unserialize() を選ぶのが基本です。

__set_state():var_exportの復元時に使われるメソッド

__set_state() の基本

__set_state() は、var_export() が出力したオブジェクト表現をPHPコードとして再評価し、オブジェクトを復元するときに使われるマジックメソッドです。

__set_state() は、static メソッドとして定義します。

class User
{
    public string $name;

    public static function __set_state(array $properties): object
    {
        $user = new self();
        $user->name = $properties['name'];

        return $user;
    }
}

$user = new User();
$user->name = '田中';

var_export($user);

var_export() は、変数の内容をPHPコードとして表現する関数です。

オブジェクトを復元する可能性がある場合、__set_state() を定義しておくことで、復元時の処理を制御できます。

__set_state() の注意点

var_export() は、対象クラスに __set_state() が実装されているかどうかを確認せずに出力する場合があります。

そのため、出力されたコードを再評価して復元しようとしたときに、__set_state() がないとエラーになる可能性があります。

var_export() の結果をオブジェクト復元に使う設計であれば、__set_state() を定義しておくと安全です。

マジックメソッドを使うメリット

コードを直感的に書ける

マジックメソッドを使うと、内部処理を隠しながら、外部からは自然な書き方にできます。

たとえば、次のようなコードです。

echo $user->name;

一見すると、単純なプロパティアクセスに見えます。

しかし内部では、__get() によって配列やデータベース由来の値を取得している場合があります。

このように、使う側のコードをシンプルにできるのがメリットです。

柔軟なAPIを作れる

__call()__get() を使うと、柔軟なAPIを作れます。

たとえば、次のような書き方です。

$user->profile;
$query->whereName('田中');

このようなコードは、フレームワークやORMでよく見られます。

実際には、存在しないプロパティやメソッドをマジックメソッドで受け取り、内部で適切な処理に変換しています。

オブジェクトの表現力を高められる

__toString() を使うと、オブジェクトを文字列として自然に表現できます。

echo $email;

__invoke() を使うと、オブジェクトを関数のように扱えます。

$result = $validator($value);

このように、マジックメソッドを使うことで、オブジェクトの使い方に幅を持たせることができます。

マジックメソッドを使うデメリット

処理の流れが分かりにくくなる

マジックメソッドは自動的に呼ばれるため、コードを読んだだけでは処理の流れが分かりにくくなることがあります。

たとえば、次のコードです。

echo $user->profile;

一見すると、単純にプロパティを読み取っているだけに見えます。

しかし、内部で __get() が呼ばれ、さらにデータベースアクセスが発生しているかもしれません。

このように、見た目以上に重い処理が隠れていると、パフォーマンス問題やデバッグの難しさにつながります。

タイポに気づきにくくなる

__get()__set() を使うと、存在しないプロパティ名でも処理できてしまいます。

$user->emial = 'test@example.com';

本当は email と書くべきところを emial と間違えていても、__set() がそのまま受け取ってしまう可能性があります。

このようなミスを防ぐためには、許可リストを用意したり、不正なプロパティ名に対して例外を投げたりすることが大切です。

IDEや静的解析と相性が悪い場合がある

マジックメソッドによって動的にプロパティやメソッドを扱うと、IDEや静的解析ツールが正しく認識できない場合があります。

たとえば、実際には __get() によって $user->name が取得できるとしても、クラスに public string $name が定義されていなければ、ツール側からは未定義プロパティに見えることがあります。

この場合は、PHPDocで補足するとよいです。

/**
 * @property string $name
 * @property string $email
 * @method string getName()
 */
class User
{
    private array $data = [];

    public function __get(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
}

PHPDocを適切に書くことで、コード補完や静的解析の精度を高められます。

セキュリティリスクになる場合がある

特に注意したいのが、unserialize() とマジックメソッドの組み合わせです。

外部から受け取った信頼できないデータを unserialize() すると、意図しないオブジェクトが復元され、__wakeup()__unserialize()__destruct() などが呼ばれる可能性があります。

$data = unserialize($_POST['data']); // 危険

外部入力に対しては、安易に unserialize() を使わないようにしましょう。

代替として、JSONを使う方法があります。

$data = json_decode($_POST['data'], true);

特に、ユーザー入力、Cookie、POSTデータ、外部APIのレスポンスなどを処理する場合は注意が必要です。

マジックメソッドを使うべき場面

オブジェクト生成時に必ず初期化したい場合

__construct() は、オブジェクト生成時に必要な値を設定したい場合に使います。

class User
{
    public function __construct(
        public string $name,
        public string $email
    ) {}
}

これは自然で読みやすく、実務でもよく使われます。

必須データをコンストラクタで受け取ることで、不完全なオブジェクトを作りにくくなります。

オブジェクトを文字列として表現したい場合

オブジェクトを文字列として自然に扱いたい場合は、__toString() が便利です。

class Money
{
    public function __construct(
        private int $amount
    ) {}

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

echo new Money(1000);

実行結果は次のようになります。

1,000円

金額、メールアドレス、URL、名前など、文字列表現を持つ値オブジェクトで使いやすいメソッドです。

オブジェクトを関数のように扱いたい場合

単一の処理を表すクラスでは、__invoke() が便利です。

class SlugGenerator
{
    public function __invoke(string $text): string
    {
        return strtolower(str_replace(' ', '-', $text));
    }
}

$generateSlug = new SlugGenerator();

echo $generateSlug('Hello World');

実行結果は次のようになります。

hello-world

処理そのものをオブジェクトとして扱いたいときに向いています。

フレームワークやライブラリのような柔軟なAPIを作りたい場合

__get()__call() は、フレームワークやライブラリの内部で使われることがあります。

たとえば、次のような柔軟な書き方を実現したい場合です。

$user->profile;
$query->whereName('田中');

このような設計では、呼び出し側のコードを短く直感的にできます。

ただし、内部処理が見えにくくなるため、ドキュメントやPHPDocを整備することが大切です。

マジックメソッドを避けたほうがよい場面

通常のプロパティやメソッドで十分な場合

単純なデータを扱うだけなら、マジックメソッドを使う必要はありません。

class User
{
    public function __construct(
        public string $name,
        public string $email
    ) {}
}

このように通常のプロパティで十分な場合は、そのほうが読みやすくなります。

無理に __get()__set() を使うと、かえって分かりにくくなることがあります。

型安全性を重視したい場合

__get()__set() は、プロパティ名を文字列で受け取ります。

public function __get(string $name): mixed
{
    // $name は文字列
}

そのため、タイポや不正なプロパティ名に弱くなりがちです。

型安全性を重視する場合は、明示的なメソッドを用意したほうが安全です。

$user->getEmail();
$user->changeEmail('test@example.com');

このように書けば、メソッド名の補完や静的解析が効きやすくなります。

重い処理を隠したくない場合

マジックメソッドの中で重い処理を行うと、呼び出し側から処理の重さが見えにくくなります。

たとえば、次のようなコードです。

$user->orders;

単純なプロパティアクセスに見えますが、内部で大量の注文データを取得していると、パフォーマンス問題の原因になります。

データベースアクセスや外部API通信などの重い処理は、明示的なメソッド名にしたほうが分かりやすい場合があります。

$user->loadOrders();
$user->getOrders();

処理の意図を明確にしたい場合は、マジックメソッドで隠しすぎないことが大切です。

実務でマジックメソッドを使うときのポイント

不正なアクセスには例外を投げる

__get()__set() で、存在しないプロパティ名を何でも受け入れると、バグに気づきにくくなります。

public function __get(string $name): mixed
{
    return $this->data[$name] ?? null;
}

この実装では、タイポしても null が返るだけです。

実務では、存在しないプロパティにアクセスされたら例外を投げるほうが安全な場合があります。

public function __get(string $name): mixed
{
    if (!array_key_exists($name, $this->data)) {
        throw new InvalidArgumentException("{$name} は存在しません");
    }

    return $this->data[$name];
}

このようにすると、不正なアクセスを早期に発見できます。

許可するプロパティやメソッドを明確にする

__set()__call() では、許可する名前を明確にしておくことが重要です。

private array $allowed = ['name', 'email'];

public function __set(string $name, mixed $value): void
{
    if (!in_array($name, $this->allowed, true)) {
        throw new InvalidArgumentException("{$name} は設定できません");
    }

    $this->data[$name] = $value;
}

許可リストを用意すると、タイプミスや意図しない値の設定を防ぎやすくなります。

PHPDocを活用する

マジックメソッドで動的なプロパティやメソッドを扱う場合は、PHPDocで補足すると保守しやすくなります。

/**
 * @property string $name
 * @property string $email
 * @method string getName()
 * @method string getEmail()
 */
class User
{
    // ...
}

PHPDocを使うことで、IDEの補完や静的解析ツールが動的なプロパティやメソッドを認識しやすくなります。

特に、チーム開発ではこのような補足が重要です。

シリアライズには慎重になる

serialize()unserialize() を使う場合は、セキュリティに注意が必要です。

特に、外部入力に対して unserialize() を使うのは避けるべきです。

$data = unserialize($_COOKIE['data']); // 危険

信頼できないデータを扱う場合は、JSONを使うほうが安全です。

$data = json_decode($_COOKIE['data'], true);

また、新しくシリアライズ制御を書く場合は、__sleep() / __wakeup() ではなく、__serialize() / __unserialize() を使うのがおすすめです。

初心者がまず覚えるべきマジックメソッド

最初に覚えるべきもの

初心者が最初に覚えるべきマジックメソッドは、__construct() です。

class User
{
    public function __construct(
        public string $name
    ) {}
}

__construct() は、PHPでクラスを扱うなら非常によく使います。

オブジェクト生成時に必要な値を設定できるため、実務でも頻出です。

次に覚えるとよいもの

次に覚えるとよいのは、以下のマジックメソッドです。

メソッド理由
__get()動的なプロパティアクセスを理解できる
__set()動的なプロパティ代入を理解できる
__call()存在しないメソッド呼び出しの仕組みを理解できる
__toString()オブジェクトの文字列表現を理解できる

これらを理解すると、PHPフレームワークやライブラリのコードが読みやすくなります。

特に、Laravel、CakePHP、WordPress関連のコードを読む場合、マジックメソッドの理解は役立ちます。

余裕があれば覚えるもの

余裕があれば、次のマジックメソッドも覚えておくとよいです。

メソッド用途
__invoke()オブジェクトを関数のように扱う
__clone()オブジェクト複製時の処理を制御する
__debugInfo()var_dump() の表示内容を制御する
__serialize()シリアライズ時のデータを制御する
__unserialize()復元時の処理を制御する

毎日使うものではありませんが、知っておくと設計の幅が広がります。

マジックメソッドの総合例

複数のマジックメソッドを使ったサンプル

最後に、複数のマジックメソッドを組み合わせた例を見てみます。

class User
{
    private array $data = [];

    private array $allowed = ['name', 'email'];

    public function __construct(string $name, string $email)
    {
        $this->data['name'] = $name;
        $this->data['email'] = $email;
    }

    public function __get(string $name): mixed
    {
        if (!array_key_exists($name, $this->data)) {
            throw new InvalidArgumentException("{$name} は存在しません");
        }

        return $this->data[$name];
    }

    public function __set(string $name, mixed $value): void
    {
        if (!in_array($name, $this->allowed, true)) {
            throw new InvalidArgumentException("{$name} は設定できません");
        }

        $this->data[$name] = $value;
    }

    public function __isset(string $name): bool
    {
        return isset($this->data[$name]);
    }

    public function __toString(): string
    {
        return "{$this->data['name']} <{$this->data['email']}>";
    }
}

$user = new User('田中', 'tanaka@example.com');

echo $user->name . PHP_EOL;

$user->email = 'new@example.com';

echo $user . PHP_EOL;

var_dump(isset($user->email));

実行結果は次のようになります。

田中
田中 <new@example.com>
bool(true)

この例では、次のマジックメソッドを使っています。

メソッド役割
__construct()初期値を設定する
__get()$user->name の読み取りを処理する
__set()$user->email = ... の代入を処理する
__isset()isset($user->email) を処理する
__toString()echo $user の文字列表現を返す

サンプルコードのポイント

この例では、__get()__set() で不正なプロパティ名を受け入れないようにしています。

if (!array_key_exists($name, $this->data)) {
    throw new InvalidArgumentException("{$name} は存在しません");
}
if (!in_array($name, $this->allowed, true)) {
    throw new InvalidArgumentException("{$name} は設定できません");
}

このようにしておくと、タイポや想定外のプロパティアクセスに気づきやすくなります。

マジックメソッドを使う場合は、便利さだけでなく、安全性や保守性も意識することが大切です。

まとめ

PHPのマジックメソッドは自動的に呼び出される特別なメソッド

PHPのマジックメソッドとは、オブジェクトに対して特定の操作が行われたときに、PHPが自動的に呼び出す特別なメソッドです。

代表的なものには、次のようなメソッドがあります。

メソッド役割
__construct()オブジェクト生成時の初期化
__destruct()オブジェクト破棄時の後処理
__get()存在しない、またはアクセスできないプロパティの読み取り
__set()存在しない、またはアクセスできないプロパティへの代入
__call()存在しない、またはアクセスできないメソッドの呼び出し
__callStatic()存在しない、またはアクセスできない静的メソッドの呼び出し
__toString()オブジェクトの文字列表現
__invoke()オブジェクトを関数のように呼び出す
__clone()オブジェクト複製時の処理
__debugInfo()var_dump() 表示の制御
__serialize() / __unserialize()シリアライズと復元処理の制御

マジックメソッドを使うと、柔軟で直感的なコードを書けます。

一方で、処理が暗黙的に実行されるため、使いすぎるとコードの流れが分かりにくくなります。

実務では必要な場面だけ使うことが大切

マジックメソッドは便利ですが、何でもマジックメソッドで処理すればよいわけではありません。

実務では、次の方針で使うとよいです。

方針内容
基本は明示的に書く通常のプロパティやメソッドで済むならそれを優先する
必要な場面だけ使うORM、プロキシ、値オブジェクト、フレームワーク的なAPIなどで使う
例外処理を入れる不正なプロパティ名やメソッド名を黙って受け入れない
PHPDocを書くIDE補完や静的解析を助ける
シリアライズに注意する外部入力に対して unserialize() を安易に使わない

初心者のうちは、まず __construct() をしっかり理解しましょう。

そのうえで、__get()__set()__call()__toString() を理解すると、PHPのクラスやフレームワークのコードがかなり読みやすくなります。

マジックメソッドは、PHPのオブジェクト指向をより柔軟にする強力な仕組みです。

ただし、強力であるぶん、使い方を間違えると保守性や安全性を下げる原因にもなります。

「便利だから使う」のではなく、「この場面ではマジックメソッドを使うと設計が分かりやすくなる」と判断できる場合に使うのが理想です。

以上、PHPのマジックメソッドとはなんなのかについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次