便利だけど使いどころが難しいPHPの代表的なマジックメソッドと無名関数の使い方:PHPオブジェクト指向プログラミング入門(4)(1/3 ページ)
「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する連載。今回は、マジックメソッドの概要と__get()、__set()、__call()、__callStatic()、__toString()の書き方と使い方に加え、PHP 5.3から使えるようになった無名関数と__invoke()について解説します。
本連載では、第1回の「PHPにおけるクラスの書き方と呼び出し方――インスタンス、メソッド、プロパティ」、第2回の「大規模PHP開発で欠かせないアクセス修飾子とカプセル化、アクセサー、コンストラクター/デストラクター」、第3回の「PHPにおける継承、オーバーライド、protected、parentの書き方と使い方、継承の設計」でPHPを例とした、オブジェクト指向について学んできました。
これまでは、どちらかというと、どのプログラミング言語でも共通である、あるいは、少なくとも通用する内容を解説してきましたが、今回は、PHPに固有(に近い)仕様である、「マジックメソッド」について説明します。
PHPの「マジックメソッド」とは
PHPのマジックメソッドは「インスタンスがある特定の条件になったときに、明示的にcallしなくても暗黙的にcallされるメソッド」の総称です。マジックメソッドは名前の先頭2文字がアンダースコア(__)になっているので、分かりやすいと思います。
またこの仕様があるために、自作のメソッド名の命名規則について「アンダースコア2文字で始まるメソッド名は、できるだけ避ける方がよい」と言われることも多いです。
コンストラクターとデストラクターについて、あらためて確認
連載第2回と第3回で出てきた「コンストラクター」「デストラクター」ですが、実はこれも、PHPではマジックメソッドの一つです。
コンストラクターはインスタンスが生成されるタイミングで暗黙的にcallされるメソッドです。名前が「__construct」なので、確かに名前の先頭2文字がアンダースコアになっています。
デストラクターはインスタンスが破棄されるタイミングで暗黙的にcallされるメソッドです。名前が「__destruct」なので、これも確かに名前の先頭2文字がアンダースコアになっています。
これ以外にもPHPにはさまざまなマジックメソッドがあります。コンストラクター/デストラクター以外のマジックメソッドについて、そのいくつかを見てみましょう。
アクセス不能プロパティを読み/書きする__get()、__set()の基本的な使い方
比較的よく使われるマジックメソッドに、「__get()」「__set()」があります。
__get()は「アクセス不能プロパティからデータを読み込む」タイミングで呼ばれるマジックメソッドで、__set()は「アクセス不能プロパティへデータを書き込む」タイミングで呼ばれるマジックメソッドです。「アクセス不能プロパティ」とは、外部から見たらアクセスができないプロパティで、例えば「private」なプロパティが該当します。
マジックメソッド__get()と__set()が呼ばれるタイミングを把握するために、簡単にコードを書いて理解していきましょう。
初めに、特にマジックメソッドを書かずに、アクセス不能プロパティ(4行目)に対してデータの読み書きをしてみるリスト1のコードを書いて実行してください。
<?php // class hoge { private $i_; } // $obj = new hoge(); var_dump($obj->i_); $obj->i_ = 1;
「Fatal error: Cannot access private property hoge::$i_」というエラーが出て、プログラムが異常終了します。動作としては予想通りです。
では、リスト1にマジックメソッドを加えてみます(リスト2の4〜10行目)。
<?php // class hoge { public function __get($name) { echo "get: name is {$name}\n"; return 'dummy'; } public function __set($name, $val) { echo "set: {$name} <= {$val}\n"; } // private $i_; } // $obj = new hoge(); var_dump($obj->i_); $obj->i_ = 1; var_dump($obj);
実行すると、エラーは発生せず、__get()と__set()が呼ばれました。
get: name is i_ string(5) "dummy" set: i_ <= 1 object(hoge)#1 (1) { ["i_":"hoge":private]=> NULL }
リスト2の4行目のように、__getの引数は一つで、その引数には取得したいプロパティ名が入ってきます。__getの戻り値をリスト2の16行目で受け取っていることが結果2の2行目から分かります。
また、リスト2の8行目のように、__setの引数は二つで、第一引数は__getと一緒でプロパティ名で、第二引数には代入したい値です。リスト2の18行目の結果から、__setでは表示は行うがそれ以外は何もしていないので、実際のインスタンスのプロパティには何の影響も及ぼしていないことが見て取れます。
__get()と__set()で不用意なアクセスを防御してみよう
privateなプロパティはアクセス不能プロパティなので、__setや__getが動くことが分かりました。では「存在しないプロパティにアクセスをしようとすると、どのような動きになるのか」を調べてみましょう。
リスト3は、リスト2から単純にプロパティ(private $i_;)を削除したものです。
<?php // class hoge { public function __get($name) { echo "get: name is {$name}\n"; return 'dummy'; } public function __set($name, $val) { echo "set: {$name} <= {$val}\n"; } } // $obj = new hoge(); var_dump($obj->i_); $obj->i_ = 1; var_dump($obj);
「存在しないプロパティ」もまた「アクセス不能プロパティ」の一種なので、適切に__setや__getが呼ばれます。
しかし、ここでprivateなプロパティと存在しないプロパティで、挙動が変わることがあります。それを確認するために、リスト3からマジックメソッドを一回、除去してみます(リスト4)。
<?php // class hoge { } // $obj = new hoge(); var_dump($obj->i_); $obj->i_ = 1; var_dump($obj); var_dump($obj->i_);
NULL object(hoge)#1 (1) { ["i_"]=> int(1) } int(1)
「存在しないプロパティ」へのアクセスの場合、privateなプロパティへのアクセスとは異なり、エラーになりません。また、「存在しないプロパティ」に対する値の代入では「新たに、そのプロパティが作成される」という挙動をするために、自動でプロパティが増えるのがリスト4の9行目の結果から分かります。
これが意図した挙動であればよいのですが、例えばリスト5の「data」と「date」のようなタイポ(書き間違い)があったときにエラーを見つけるのは、いささか至難になります。ちなみに「data」と「date」は、大変に似ている上にどちらも「恒常的によく使う変数名」で、本当に間違えやすいので、ぜひご注意いただければと思います。
<?php // class hoge { public $date_; } // $obj = new hoge(); $obj->data_ = 1; var_dump($obj->date_);
リスト5の結果はエラーになりません。
NULL
「存在しないプロパティへのアクセスは、そのプロパティを追加する」という動きがPHPの仕様になってしまっているので、基本的には回避のしようがありません。しかし困るときは困ります。
筆者としては「だからプロパティはprivateにしてアクセサー経由で」と思うのですが、どうしてもプロパティをpublicにしたい場合、__setと__getを使って存在しないプロパティへのアクセスからの防御ルーチンを書くことができます。
簡単にですが、リスト5に追加してみましょう(リスト6)。
<?php // class hoge { public function __get($name) { throw new Exception("存在しないプロパティ {$name} へのgetアクセス"); } public function __set($name, $val) { throw new Exception("存在しないプロパティ {$name} へのsetアクセス"); } public $date_; } // $obj = new hoge(); //$obj->data_ = 1; // コメントを外すと例外が投げられる //var_dump($obj->data_); // コメントを外すと例外が投げられる var_dump($obj->date_);
実際に「例外を投げるかどうか」は設計次第ですが、個人的には「どの道NGなので、例外で処理を止めてしまった方がデバッグしやすい」と思います。
このように、__setや__getは、もちろんアクセス不能プロパティにアクセスさせるためにも用いられますが、使い方によっては存在しないプロパティへのアクセスをブロックするためにも使えるので、覚えておくと大変に便利です。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 初心者がPHPプログラミングを始めるための基礎知識とXAMPPのインストール
オープンソースのWeb開発向けスクリプト言語「PHP」の文法を一から学ぶための入門連載。初回は、PHPの概要や特徴を紹介し、環境構築や「Hello World!」までの手順などを解説します【PHP 7.1含め2017年の情報に合うように対応、XAMPP環境構築を追記】。 - PHP編に突入! まずはソースをダウンロード
今回からPHPのビルドとインストールに入ります。まずはPHPという言語の概要と、ソースコードの入手法を解説します(編集部) - 理論編:PHPについて知ろう
スクリプトを記述するだけの簡単なプログラミングで、Web対応の高速なデータベースアプリケーションを実現する手法としてPHPが急速に注目を集めている。しかもデータベースはオープンソースのPostgreSQLだけでなく、Oracleなどの商用データベースも扱える。ここでは、5月にバージョンアップしたばかりのPHP4によるWeb-DBシステム構築法を紹介しよう。 - Mac OS X+PHPでオールインワン環境(インストール編):Mac内にPHP4、5、6を同居させるコツ
PHP4の開発は終了したが、移行の問題は残されている。異なるバージョンのPHPをスムーズに切り替えるには? - @IT自分戦略研究所 資格辞典:PHP技術者認定試験
- 自分をプログラミング言語に例えると何?
2013年1月16日、エンジニアたちが集うトークライブイベント「TechLION vol.11」が開催された。今回のレポートでは、「自分をプログラミング言語に例える」というお題に答えた5人の技術者の回答をみていく。