連載
» 2018年04月04日 05時00分 公開

Web業界で働くためのPHP入門(16):PHPの「マジックメソッド」とは――「__set()」「__get()」「__invoke()」の使い方 (1/2)

オープンソースのWeb開発向けスクリプト言語「PHP」の文法を一から学ぶための入門連載。今回は、クラスにもともと備わっている特殊なメソッドとして、「マジックメソッド」を扱います。

[齊藤新三(著)/山田祥寛(監修),WINGSプロジェクト]

マジックメソッドとは

 オープンソースのWeb開発向けスクリプト言語「PHP」の文法を一から学ぶための入門連載「Web業界で働くためのPHP入門」。今回はクラスにもともと備わっている特殊なメソッドとして、マジックメソッドを扱います。

 マジックメソッドとは、ひと言で説明すると「特殊な状況で実行されるメソッド」のことです。

 実は、マジックメソッドの1つが既に登場しています。第14回で紹介したコンストラクタ「__construct()」がそうです。コンストラクタはインスタンスが生成されるという「特殊な状況で実行されるメソッド」です。同時に紹介したデストラクタ「__destruct()」もそうです。

 このように、マジックメソッドは「特殊な状況」それぞれに対応したメソッドであり、各状況でメソッド名が決まっています。しかも、メソッド名は必ずアンダースコア2個「__」から始まっています。

 どういったマジックメソッドが存在するかを表1にまとめます。表中の記述について少し説明しておきます。引数に「任意」とあるものは、引数の型、個数ともに自由に記述できるという意味です。もちろんなくても構いません。戻り値に「任意」と書かれている場合は戻り値の型が自由という意味です。実行される状況に「アクセス不能な」とあるのは、そのメンバが存在しない、もしくは、privateです。

表1 マジックメソッド一覧
メソッド 引数 戻り値 実行される状況
__construct() 任意 なし インスタンスが生成されるとき
__destruct() なし なし インスタンスが破棄されるとき
__call() 任意 任意 アクセス不能な非staticメソッドを実行しようとしたとき
__callStatic() 任意 任意 アクセス不能なstaticメソッドを実行しようとしたとき
__get() プロパティ名 任意 アクセス不能なプロパティからデータを読み込もうとしたとき
__set() プロパティ名と値の2個 なし アクセス不能なプロパティにデータを格納しようとしたとき
__isset() プロパティ名 true/falseのどちらか アクセス不能なプロパティに対してisset()、あるいは、empty()を実行しようとしたとき
__unset() プロパティ名 なし アクセス不能なプロパティに対してunset()を実行しようとしたとき
__sleep() なし 配列 インスタンスに対してserialize()を実行したとき
__wakeup() なし なし インスタンスに対してunserialize()を実行したとき
__toString() なし 文字列 インスタンスを文字列に変換しなければならないとき
__invoke() 任意 任意 インスタンスを関数のように実行しようとしたとき
__set_state() 配列 インスタンス インスタンスに対してvar_export()を実行しようとしたとき
このメソッドはstatic
__clone() なし なし cloneキーワードを使ってインスタンスのクローン生成を実行したとき
__debugInfo() なし 配列 インスタンスに対してvar_dump()を実行しようとしたとき

 なお、このように「__」から始まるメソッド名は特殊なメソッドとして使われていますので、自分で作成するメソッド、関数には「__」で始まる名前を付けないようにしてください。

 これら、マジックメソッドは、特殊な状況で実行されるため、__construct()を除くとなかなか使いどころが難しいものがほとんどです。その中でも比較的使いやすいもの、よく見掛けるものとして、本稿では、「__set()」「__get()」「__invoke()」をサンプルを交えながら紹介していきます。

アクセス不能プロパティへのアクセスで実行される__set()と__get()

 まず、__set()と__get()です。__set()と__get()は表1にあるように、アクセス不能なプロパティにアクセスしたときに実行されるメソッドです。

 「アクセス不能なプロパティ」とは、先述のように、privateプロパティか、そもそも存在しないプロパティです。ここでは、あえてプロパティを書かずに、つまり存在しない状態を作り、代わりに、privateな配列プロパティを設定し、それと__set()と__get()を組み合わせることで、動的プロパティを実現した例を挙げます。

__set()と__get()と配列プロパティの組み合わせ

 具体例を見ていきましょう。以下のDynamicPropertyクラスを作成してください。

<?php
class DynamicProperty
{
    private $props = [];  //(1)
 
    public function __set($name, $value)  //(2)
    {
        $this->props[$name] = $value;  //(3)
    }
 
    public function __get($name)  //(4)
    {
        if(isset($this->props[$name])) {  //(5)
            return $this->props[$name];  //(6)
        } else {
            return null;  //(7)
        }
    }
}
リスト1 phplesson/chap16/DynamicProperty.php

 リスト1の(2)と(4)にマジックメソッド__set()と__get()が使われています。処理内容としては、(2)の__set()は引数でもらったプロパティ名と値をそのまま(1)で用意したprivateな配列プロパティ$propsに格納しています。(4)の__get()の処理の中心は(6)です。(5)と(7)は後述しますが、(6)は引数でもらったプロパティ名を使って(1)の配列プロパティから値を取得し、リターンしています。

プロパティを動的に扱えるようになる

 このクラスを利用するとどういった便利なことが起こるのでしょうか。次に、そこを見ましょう。DynamicPropertyクラスを利用する以下のuseDynamicProperty.phpを作成、実行してください。

<?php
require_once("DynamicProperty.php");
 
$dm = new DynamicProperty();  //(1)
$dm->name = "田中";  //(2)
$dm->english = 88;  //(2)
$dm->math = 91;  //(2)
$dm->japanese = 85;  //(2)
 
$total = $dm->english + $dm->math + $dm->japanese;  //(3)
print($dm->name."さんの3教科合計: ".$total);  //(4)
print("<br>理科: ".$dm->science);  //(5)
リスト2 phplesson/chap16/useDynamicProperty.php

 実行結果は以下の通りです。

田中さんの3教科合計: 264
理科:

 リスト2の実行部分でのポイントは、(2)です。(1)で生成したDynamicPropertyインスタンスに対して、(2)でname、english、math、japaneseの各プロパティに値を代入しています。ところが、リスト1のDynamicPropertyクラスの記述を見ても分かるように、これらのプロパティはクラスのどこにも定義されていません。通常ならばエラーとなるところを、マジックメソッド__set()が定義されているためにエラーとならずに__set()メソッド内の処理が実行されます。

 そして、先述の通り、そのメソッド内の処理として配列プロパティ$propsにname、english、math、japaneseの各値が格納されます(図1)。

図1 __set()を使って値が格納されていく流れ

 このように、マジックメソッド__set()と配列プロパティを使ったクラスを作成すると、そのクラスを使う側からは、任意のプロパティを格納できる、それこそマジックのようなインスタンスを生成することが可能となります。

__get()の方は少し注意が必要

 この仕組みに合わせて__get()の方も記述すると、いったん格納した値を自由に取り出すことが可能となります。それが、リスト1の__get()内の記述である(6)であり、それを使って値を取り出しているのがリスト2の(3)や(4)です。

 ところで、値を格納する__set()の場合はいいのですが、格納した値を取り出す__get()の場合は、取り出す値が存在しない場合があります。その確認のためのコードがリスト2の(5)です。ここではscienceプロパティ、つまり、$propsのキーがscienceの値を取り出そうとしています。もし、__get()内の処理が以下のように(6)の1行の場合、リスト2の(5)で「Notice: Undefined index: science」というエラーが発生します。

public function __get($name)
{
    return $this->props[$name];
}

 これを避けるために、$propsに該当キーで値が存在するかをいったんチェックしているのがリスト1の(5)です。ここでは変数が存在するかどうかをチェックする関数isset()を使って存在チェックを行っています。存在する場合は、(6)のように$props内の値をリターンしています。一方、存在しない場合はnullをリターンするようにしています(7)。これにより、エラーを回避しています。

__set()と__get()の利用は慎重に

 リスト1のDynamicPropertyのようにマジックメソッドと配列プロパティを使ったクラスというのは、動的にプロパティを設定できるようになり、非常に柔軟に値を格納できるインスタンスを生成できます。

 例えば、データベース内のデータを各プロパティに格納するクラスを考えるとします。このようなクラスを「エンティティクラス」といいますが、データベースの構造が頻繁に変更になるような場合、各プロパティを固定で記述していると変更対応が大変な作業となります。そう行った場合、ここで紹介した動的プロパティを利用すると便利です。実際、各種フレームワーク内ではこのような記述を使ったクラスがあります。

 一方で、何でもかんでも値を格納できてしまうため、思わぬ誤動作を招く可能性があります。フレームワークの場合は、しっかり設計した上でこれらの__set()や__get()を利用していますが、ただ便利さにだけ目を向けて独自クラスに組み込むのは避けた方がいいでしょう。

       1|2 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

@ITのメールマガジン(無料)

✔ 【@IT通信】
  編集部のおすすめ記事、限定コラムをお届け
✔ 【@IT新着速報】
  新着記事・速報をまとめてお届け
✔ 【@IT自分戦略研究所Weekly】
  転職支援情報やキャリアアップ情報をお届け

RSSについて

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

メールマガジン登録

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