PHPにはマジックメソッドという機能があります。オブジェクトに対し、特定の動作を行った際に動作する(魔法のような?)メソッドのことです。
さて、そんなマジックメソッドなのですが、何となく使っている。もしくはまったく使ってないよ!と思われる方も多いのではないでしょうか。
たいした機能ではないのか……と言えば全くそんなことはなく、フレームワーク内の裏側では使われていることもあります。つまり上手く使えば便利な機能であることは間違いありません。
というわけで本ページでは、そんなマジックメソッドを「知る」まではいかなくとも「こんな機能のがあるよ」という確認用。かなりざっくりのご紹介です。
とりあえず俯瞰して全体をチェックしたいな、という場合に向いているページです。
詳細な解説ページは、ぼちぼちと作っていければいいなと思っています。
マジックメソッド一覧
執筆時(PHP8.3)のマジックメソッド一覧です。
メソッド名 | 概要 |
---|---|
__construct() | クラスの新しいインスタンスが作成されるときに自動的に呼び出される。 |
__destruct() | オブジェクトがガベージコレクションされる前やスクリプトの終了時に呼び出される。 |
__call($name, $arguments) | オブジェクトからアクセス不可能なメソッドが呼び出されたときに実行。 |
__callStatic($name, $arguments) | アクセス不可能な静的メソッドが呼び出されたときに実行。 |
__get($name) | アクセス不可能なプロパティにアクセスしようとするときに呼び出される。 |
__set($name, $value) | アクセス不可能なプロパティに値を設定しようとするときに呼び出される。 |
__isset($name) | isset()関数をアクセス不可能なプロパティに使用したときに呼び出される。 |
__unset($name) | unset()関数をアクセス不可能なプロパティに使用したときに呼び出される。 |
__sleep() | serialize()関数がオブジェクトに適用される前に呼び出される。オブジェクトの保存すべきプロパティ名の配列を返す必要がある。 |
__wakeup() | unserialize()関数でオブジェクトが再構築されるときに呼び出される。 |
__serialize() | オブジェクトの状態をシリアライズするときに使用。プロパティの連想配列を返す。 |
__unserialize($data) | シリアライズされたデータからオブジェクトを再構築。 |
__toString() | オブジェクトを文字列として扱おうとするとき(例:echoで出力)に呼び出される。 |
__invoke($arguments) | オブジェクトを関数のように呼び出そうとするときに実行。 |
__set_state($array) | var_export()関数でエクスポートされたクラスの配列表現を受け取って、新しいクラスのインスタンスを生成するのに使用。 |
__clone() | オブジェクトがクローンされるときに呼び出される。 |
__debugInfo() | var_dump()関数でオブジェクトがダンプされるときに返すべき配列データを返す。 |
全部で17種類あります。
これから1つずつざっくりと説明していきますが、全部覚える必要は全くありません。
一般的なエンジニアにとっては必要になった時に「こんなのあったな」と思い出して検索できる程度で充分ではないでしょうか。
それでは長いですが見ていきましょう。
__constructor()
これはおなじみですよね。__construct()
メソッドは、PHPのクラス内で最も頻繁に使用されるマジックメソッドです。
マジックメソッドというよりも「コンストラクタ」として認識している方も多いかもしれません。
このメソッドは、クラスの新しいインスタンスが作成される際に自動的に呼び出されます。オブジェクトの初期化や、必要な初期設定を行う目的で使用されます。
コード例
class SampleClass
{
public $name;
public $age;
public function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
echo "SampleClassのインスタンスが作成されました!";
}
}
$person = new SampleClass("Taro", 25); // この時点で__construct()が自動的に呼び出される
echo $person->name; // 出力: Taro
echo $person->age; // 出力: 25
サンプルコードでは、SampleClass
というクラスを定義し、その中に__construct()
を持っています。
クラスの新しいインスタンスがnew SampleClass("Taro", 25)
という形で作成される際に自動的に呼び出され、$name
と$age
の値がそれぞれオブジェクトのプロパティに設定されます。
以前はJava等と同じく、クラス名と同じメソッドがコンストラクタだったのですが、この方式の方が固定なので使いやすいと感じます。
__destruct()
__destruct()
メソッドは、クラスのインスタンスが破棄される際に自動的に呼び出されるマジックメソッドです。__contruct()
の真逆ですね。
PHPのガベージコレクションによってオブジェクトがメモリから解放される直前、またはスクリプトの実行が完了する時点でこのメソッドが呼び出されます。
リソースの解放や、終了時に行う必要がある特定の操作を、このメソッド内で行うことができます。
コード例
以下の例では、FileHandler
クラスを定義しています。このクラスは、ファイルを開いて内容を書き込むためのシンプルなハンドラとして機能します。
class FileHandler
{
private $file;
public function __construct($filename)
{
$this->file = fopen($filename, 'w');
echo "ファイルが開かれました!<br>";
}
public function write($content)
{
fwrite($this->file, $content);
}
public function __destruct()
{
fclose($this->file);
echo "ファイルが閉じられました!<br>";
}
}
$fileHandler = new FileHandler('sample.txt');
$fileHandler->write('Hello, World!');
// スクリプトの終了時、またはオブジェクトが解放されると、__destruct() が自動的に呼び出される。
__construct()
でファイルを開き、__destruct()
でファイルを閉じる操作を自動的に行います。
このように、__destruct()
はリソースのクリーンアップを自動化するのに便利なメソッドとなります。
__call()
__call()
は、クラスのオブジェクトからアクセス不可能なメソッドが呼び出されたときに自動的に実行されるマジックメソッドです。
このメソッドは、メソッドの名前と、そのメソッドに渡される引数を受け取ります。
動的なメソッドの呼び出しや、存在しないメソッドの呼び出しに対してエラーをカスタマイズするときなどに役立ちます。
コード例
class Robot
{
private function greet()
{
echo "Hello, Human!<br>";
}
public function __call($method, $arguments)
{
if ($method === 'sayHello') {
$this->greet();
} else {
echo "Unknown method: {$method}<br>";
}
}
}
$robot = new Robot();
$robot->sayHello(); // 出力: Hello, Human!
$robot->doTask(); // 出力: Unknown method: doTask
上記の例では、Robot
クラスには greet()
というプライベートメソッドがあります。
sayHello
というメソッドは実際には存在しないのですが、クラスのインスタンスからそれを呼び出そうとすると__call()
が実行され、代わりにgreet()
メソッドが呼び出されます。
__callStatic()
__callStatic()
は、アクセス不可能な静的メソッドが呼び出されたときに自動的に実行されるマジックメソッドです。
__call()
がインスタンスメソッドに対して動作するのに対し、__callStatic()
はstaticな静的メソッドの呼び出しに対して動作します。
コード例
class Toolkit
{
private static function secretTool()
{
echo "Using the secret tool!<br>";
}
public static function __callStatic($method, $arguments)
{
if ($method === 'useTool') {
self::secretTool();
} else {
echo "Unknown static method: {$method}<br>";
}
}
}
Toolkit::useTool(); // 出力: Using the secret tool!
Toolkit::doTask(); // 出力: Unknown static method: doTask
上記の例では、Toolkit
クラスには secretTool()
というプライベートな静的メソッドがあります。
useTool
という静的メソッドは実際には存在しないのですが、それを呼び出そうとすると__callStatic()
が実行され、代わりにsecretTool()
メソッドが呼び出されます。
__get()
__get()
メソッドは、クラスのオブジェクトからアクセス不可能なプロパティにアクセスしようとした時に自動的に実行されるマジックメソッドです。
このメソッドは、アクセスしようとしたプロパティの名前を受け取ります。
オブジェクトの内部状態をカプセル化する際や、特定のプロパティに対するカスタムな取得処理を行いたい場合に役立ちます。
コード例
class UserProfile
{
private $data = [
'name' => 'John Doe',
'age' => 30
];
public function __get($property)
{
if (array_key_exists($property, $this->data)) {
return $this->data[$property];
} else {
return "Property {$property} does not exist!";
}
}
}
$user = new UserProfile();
echo $user->name; // 出力: John Doe
echo $user->age; // 出力: 30
echo $user->email; // 出力: Property email does not exist!
上記の例では、UserProfile
クラス内で data
配列にいくつかのプロパティが設定されています。
このクラスのインスタンスから直接 data
配列の内容にアクセスすることはできませんが、__get()
メソッドを使用することで配列内のデータにアクセスできるようになっています。
__get()や後述する__set()は、フレームワークでもよく利用されていますね!
__set()
__set()
はクラスのオブジェクトに対してアクセス不可能なプロパティに値を設定しようとしたときに自動的に実行されるマジックメソッドです。
要は__get()
の真逆であり、併せて使用されることが一般的です。
コード例
class UserProfile
{
private $data = [
'name' => 'John Doe',
'age' => 30
];
public function __get($property)
{
return $this->data[$property] ?? null;
}
public function __set($property, $value)
{
if (array_key_exists($property, $this->data)) {
$this->data[$property] = $value;
} else {
echo "Property {$property} cannot be set!";
}
}
}
$user = new UserProfile();
echo $user->name; // 出力: John Doe
$user->name = 'Jane Doe';
echo $user->name; // 出力: Jane Doe
$user->email = 'jane@example.com'; // 出力: Property email cannot be set!
上記の例では、UserProfile
クラス内で data
配列にいくつかのプロパティが設定されています。
このクラスのインスタンスから直接 data
配列の内容を変更することはできませんが、__set()
メソッドを使用することで配列内のデータの設定ができるようになっています。
__isset()
__isset()
は、isset()
または empty()
関数をクラスのオブジェクトのアクセス不可能なプロパティに対して使用したときに、自動的に実行されるマジックメソッドです。
このメソッドは、確認しようとしたプロパティの名前を受け取ります。
__get()
や __set()
と組み合わせて使用することで、プロパティの存在状態や、空かどうかのチェックをカスタマイズすることができます。
コード例
class Profile
{
private $data = [
'name' => 'Alice',
'age' => 25,
];
public function __isset($key)
{
return isset($this->data[$key]);
}
}
$profile = new Profile();
if (isset($profile->name)) {
echo "Name is set!";
} else {
echo "Name is not set!";
}
// 出力: "Name is set!"
上記の例では、Profile
クラスは非公開の配列プロパティ$data
を持っています。
外部から$profile->name
のようなアクセスを試みても、直接的にはアクセスできません。しかし、__isset()
メソッドを実装することで、isset($profile->name)
の形式でプロパティの存在確認ができるようになっています。
このように、__isset()
はprivateやprotectedなプロパティの存在を外部から確認するために利用可能です。
正直、便利な使い所があまりまだ分かっていませんので研究したいです。
__unset()
__unset()
は、unset()
関数をクラスのオブジェクトのアクセス不可能なプロパティに対して使用したときに、自動的に実行されるマジックメソッドです。
このメソッドは、削除しようとしたプロパティの名前を受け取ります。
このメソッドを使用することで、特定のプロパティの削除時にカスタムな動作を実行させることができます。
コード例
class UserProfile
{
private $data = [
'name' => 'John Doe',
'age' => 30
];
public function __get($property)
{
return $this->data[$property] ?? null;
}
public function __unset($property)
{
if (array_key_exists($property, $this->data)) {
unset($this->data[$property]);
echo "Property {$property} has been unset.<br>";
} else {
echo "Property {$property} does not exist.<br>";
}
}
}
$user = new UserProfile();
echo $user->name; // 出力: John Doe
unset($user->name); // 出力: Property name has been unset.
echo $user->name; // 出力: (何も表示されない)
unset($user->email); // 出力: Property email does not exist.
上記の例では、UserProfile
クラス内で data
配列にいくつかのプロパティが設定されています。
このクラスのインスタンスのプロパティを削除する際、__unset()
メソッドが実行され、data
配列内のプロパティの削除を行います。
__sleep()
__sleep()
は、オブジェクトをシリアライズする前に自動的に実行されるマジックメソッドです。
このメソッドは、シリアライズする際にオブジェクトから保存されるプロパティの配列を返す必要があります。
つまり、特定のプロパティをシリアライズの結果から除外したい場合や、シリアライズ前のクリーンアップを行いたい場合にこのメソッドを利用します。
コード例
class UserProfile
{
public $name = 'John Doe';
private $password = 'secret123';
public function __sleep()
{
// パスワードをシリアライズの結果から除外する
return ['name'];
}
}
$user = new UserProfile();
$serializedUser = serialize($user);
echo $serializedUser; // パスワードが含まれていないことが確認できます
上記の例では、UserProfile
クラスには公開されている name
プロパティとプライベートな password
プロパティがあります。
__sleep()
メソッドを使用して、シリアライズ時に password
プロパティを除外しています。
__wakeup()
__wakeup()
メソッドは、オブジェクトをデシリアライズする際(unserialize()
関数を使用するとき)に自動的に実行されるマジックメソッドです。要は__sleep()
の対になるメソッドですね。
このメソッドは、オブジェクトのデシリアライズ後に必要な再初期化の処理やリソースの再接続などを行うためのものです。
たとえば、データベースへの接続リソースやファイルハンドルなど、シリアライズできないリソースを持つオブジェクトがあったとします。その場合、このメソッドを使用してデシリアライズ後にリソースを再確立することができます。
コード例
class DatabaseConnection
{
private $connection;
private $dsn = 'mysql:host=localhost;dbname=test';
private $username = 'root';
private $password = '';
public function connect()
{
$this->connection = new PDO($this->dsn, $this->username, $this->password);
}
public function __sleep()
{
// データベース接続をクローズする(この例では実際の接続のクローズ処理は省略しています)
return ['dsn', 'username', 'password'];
}
public function __wakeup()
{
// デシリアライズ後にデータベースに再接続する
$this->connect();
}
}
$db = new DatabaseConnection();
$db->connect();
$serializedDB = serialize($db);
$deserializedDB = unserialize($serializedDB);
// $deserializedDB はデシリアライズ後に自動的にデータベースに接続されます
この例では、DatabaseConnection
クラスはデータベースへの接続を管理しています。
__sleep()
を使用して、シリアライズ時にデータベース接続を閉じ、__wakeup()
メソッドを使用して、デシリアライズ後にデータベースへ再接続しています。
__serialize()
PHP 7.4 から、__sleep()
と __wakeup()
のよりモダンな代替手段として、__serialize()
と __unserialize()
が導入されました。
PHP 7.4以降のプロジェクトであれば、こちらの利用を検討してみると良さそうです。
__serialize()
は、オブジェクトをシリアライズする際、どのプロパティをシリアライズに含めるかを制御するためのものです。
このメソッドは、シリアライズされるプロパティの連想配列を返す必要があります。
コード例
class UserProfile
{
private $name = 'John Doe';
private $password = 'secret123';
public function __serialize(): array
{
// passwordを除外し、nameのみをシリアライズに含める
return ['name' => $this->name];
}
}
$user = new UserProfile();
$serializedUser = serialize($user);
echo $serializedUser; // パスワードが含まれていないことが確認できます
この例では、UserProfile
クラスには name
と password
の2つのプライベートプロパティがあります。
__serialize()
を使用して、シリアライズ時に password
プロパティを除外しています。
__serialize()
は__sleep()
とは異なり、連想配列を返すことに注意が必要です。
このメソッドを利用することにより、プロパティの値を直接操作したり、新しい値を追加したりすることが容易になります。
__unserialize()
__unserialize()
は、オブジェクトをデシリアライズする際に使用されます。
このメソッドは、__serialize()
と密接に関連しており、unserialize()
関数によってオブジェクトがデシリアライズされるときに自動的に呼び出されます。
__unserialize()
は、__serialize()
が返すデータ(連想配列)を引数として受け取り、それを使用してオブジェクトの状態を復元します。
コード例
class UserProfile
{
private $name;
private $password = 'secret123'; // 初期値が設定されています
public function __serialize(): array
{
return ['name' => $this->name];
}
public function __unserialize(array $data): void
{
$this->name = $data['name'];
// passwordプロパティはデシリアライズされませんが、初期値を保持しています
}
}
$user = new UserProfile();
$serializedUser = serialize($user);
$restoredUser = unserialize($serializedUser);
// $restoredUser はデシリアライズされ、nameプロパティが復元されます
この例では、UserProfile
クラスは name
プロパティと password
プロパティを持っています。
__serialize()
メソッドを使用して、name
プロパティのみをシリアライズに含め、__unserialize()
メソッドを使用してデシリアライズ時に name
プロパティの状態を復元しています。
__toString()
__toString()
は、オブジェクトを文字列として表現する際に使用されるマジックメソッドです。
このメソッドは、echo
や print
のような文字列コンテキストでオブジェクトを使用する場合や、文字列操作関数にオブジェクトを渡す場合に自動的に呼び出されます。
このメソッドは、当然のことながら文字列を返す必要があります。
コード例
class Book
{
private $title;
private $author;
public function __construct($title, $author)
{
$this->title = $title;
$this->author = $author;
}
public function __toString()
{
return "「" . $this->title . "」 by " . $this->author;
}
}
$book = new Book("道は開ける", "デール・カーネギー");
echo $book; // 出力: 「"道は開ける」 by デール・カーネギー
この例では、Book
クラスはタイトルと著者の情報を持っています。
__toString()
メソッドを使用して、オブジェクトを文字列として表現する方法を定義しています。そのため、echo
でオブジェクトを直接出力することができます。
__invoke()
__invoke()
は、オブジェクトを関数のように呼び出そうとした時に実行されるマジックメソッドです。
特定のクラスのインスタンスをコールバック関数として利用したり、処理をカプセル化したりすることが可能です。
コード例
class Multiplier
{
private $factor;
public function __construct($factor)
{
$this->factor = $factor;
}
public function __invoke($value)
{
return $value * $this->factor;
}
}
$double = new Multiplier(2);
echo $double(5); // 出力: 10
$triple = new Multiplier(3);
echo $triple(5); // 出力: 15
この例では、Multiplier
クラスは与えられた数値に特定の係数を掛ける役割を持っています。
__invoke()
メソッドにより、$double
や $triple
のようなオブジェクトを関数のように扱うことができます。
こちらは詳細な以下ページで詳細な解説をしていますので、ご興味があれば参照ください。
__set_state()
__set_state()
は、var_export()
関数でエクスポートされたクラスの情報を基に、新しいオブジェクトのインスタンスを作成するために使用されます。
__set_state()
は、var_export()
によって出力された結果を評価 (eval()
) する際に呼び出されます。
このメソッドは静的メソッドとして定義され、引数としてプロパティの配列を受け取り、新しいオブジェクトのインスタンスを返す必要があります。
コード例
class Point
{
private $x;
private $y;
public function __construct($x, $y)
{
$this->x = $x;
$this->y = $y;
}
public static function __set_state($array)
{
return new self($array['x'], $array['y']);
}
}
$point = new Point(2, 3);
$exported = var_export($point, true);
$restoredPoint = eval("return $exported;");
// $restoredPoint は $point と同じ情報を持つ新しいインスタンスです
この例では、Point
クラスは 2D の点を表現します。
__set_state()
を使用して、var_export()
によってエクスポートされた情報から新しい Point
オブジェクトを作成しています。
ぶっちゃけ、これだけの説明では分かりづらいので、いずれ詳細ページを作ります。
__clone()
__clone()
は、clone
キーワードを使用してオブジェクトがクローンされる時に自動的に呼び出されるマジックメソッドです。
このメソッドは、オブジェクトの複製時に特定の動作や状態の調整を行う場合に便利です。
デフォルトのクローニング操作は、オブジェクトのシャローコピー(浅いコピー)を作成します。
しかし、オブジェクト内部に他のオブジェクトへの参照や特定のリソースへの参照がある場合、深いコピー(新しいオブジェクトのインスタンスを作成するなど)やリソースの再初期化などの追加操作を行うことが必要になることがあります。
このようなケースで__clone()
メソッドを使用することで、クローニング時の特定の動作をカスタマイズできます。
コード例
class DateRange
{
private $start;
private $end;
public function __construct(DateTime $start, DateTime $end)
{
$this->start = $start;
$this->end = $end;
}
public function __clone()
{
// DateTime オブジェクトをクローンすることで、新しいインスタンスを作成
$this->start = clone $this->start;
$this->end = clone $this->end;
}
}
$start = new DateTime('2022-01-01');
$end = new DateTime('2022-12-31');
$range = new DateRange($start, $end);
$clonedRange = clone $range;
// $clonedRange の start と end は新しい DateTime オブジェクトのインスタンスを参照
この例では、DateRange
クラスは2つの DateTime
オブジェクトをプロパティとして保持しています。
__clone()
内で、これらの DateTime
オブジェクトをクローンすることで、新しいインスタンスを作成しています。
__debugInfo()
__debugInfo()
は、var_dump()
関数を使用してオブジェクトをダンプしたときの出力情報をカスタマイズするためのマジックメソッドです。
このメソッドは配列を返すことが期待され、返された配列の内容が var_dump()
で表示されます。
例えば、オブジェクトが内部的な状態や大きなデータを持っていて、デバッグ出力でそれを表示したくない場合や、特定の形式でデバッグ情報を表示したい場合などに、このメソッドを使用してカスタマイズできます。
コード例
class User
{
private $id;
private $name;
private $password; // この情報はデバッグ出力に表示したくない
public function __construct($id, $name, $password)
{
$this->id = $id;
$this->name = $name;
$this->password = $password;
}
public function __debugInfo()
{
// password を除いた情報を返す
return [
'id' => $this->id,
'name' => $this->name,
'info' => 'password is hidden for security reasons'
];
}
}
$user = new User(1, "Alice", "secret1234");
var_dump($user);
この例では、User
クラスはユーザのID、名前、およびパスワードをプロパティとして保持しています。
セキュリティの観点から、__debugInfo()
を使用して、パスワードの情報はデバッグ出力に表示しないようにしています。
まとめ
お疲れ様でした!非常に長かったですね。__constructor()
のように頻繁に使うものもあれば、ほとんど使わないようなものもあります。
また、分かりやすいものもあれば、一見して機能が分かりづらいものもあります。
一通り知識として知っておくことで、何かのひょうしに「使えるかな」と思い出すことができれば良いのではないでしょうか。
なお、これらのマジックメソッドは各種フレームワークで利用されていることがあります。例えば人気のLaravelなどでも裏側で使われており、その使い方はさすがに洗練されています。
まずはそれらでの使用例を参考にしてみると良いかもしれませんね。
コメント