連載
» 2015年09月04日 05時00分 UPDATE

PHPオブジェクト指向プログラミング入門(4):便利だけど使いどころが難しいPHPの代表的なマジックメソッドと無名関数の使い方 (1/3)

「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する連載。今回は、マジックメソッドの概要と__get()、__set()、__call()、__callStatic()、__toString()の書き方と使い方に加え、PHP 5.3から使えるようになった無名関数と__invoke()について解説します。

[古庄道明,株式会社格子組]
「PHPオブジェクト指向プログラミング入門」のインデックス

連載目次

 本連載では、第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;
リスト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);
リスト2

 実行すると、エラーは発生せず、__get()と__set()が呼ばれました。

get: name is i_
string(5) "dummy"
set: i_ <= 1
object(hoge)#1 (1) {
  ["i_":"hoge":private]=>
  NULL
}
結果2

 リスト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);
リスト3

 「存在しないプロパティ」もまた「アクセス不能プロパティ」の一種なので、適切に__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_);
リスト4
NULL
object(hoge)#1 (1) {
  ["i_"]=>
  int(1)
}
int(1)
結果4

 「存在しないプロパティ」へのアクセスの場合、privateなプロパティへのアクセスとは異なり、エラーになりません。また、「存在しないプロパティ」に対する値の代入では「新たに、そのプロパティが作成される」という挙動をするために、自動でプロパティが増えるのがリスト4の9行目の結果から分かります。

 これが意図した挙動であればよいのですが、例えばリスト5の「data」と「date」のようなタイポ(書き間違い)があったときにエラーを見つけるのは、いささか至難になります。ちなみに「data」と「date」は、大変に似ている上にどちらも「恒常的によく使う変数名」で、本当に間違えやすいので、ぜひご注意いただければと思います。

<?php
//
class hoge {
    public $date_;
}
//
$obj = new hoge();
$obj->data_ = 1;
var_dump($obj->date_);
リスト5

 リスト5の結果はエラーになりません。

NULL
結果5

 「存在しないプロパティへのアクセスは、そのプロパティを追加する」という動きが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_);
リスト6

 実際に「例外を投げるかどうか」は設計次第ですが、個人的には「どの道NGなので、例外で処理を止めてしまった方がデバッグしやすい」と思います。

 このように、__setや__getは、もちろんアクセス不能プロパティにアクセスさせるためにも用いられますが、使い方によっては存在しないプロパティへのアクセスをブロックするためにも使えるので、覚えておくと大変に便利です。

       1|2|3 次のページへ

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。