phpのアクセス権の意外な罠

PHPのアクセス権で意外にもハマってしまったのでメモ。アクセス権とはクラスのメンバ変数とかメソッドにpublicとかprivateとかつけてるアレです。

今回はprivateな変数を継承先のクラスで使用したかったのでprotectedな変数した途端にエラーが発生。なぜだ。。。

アクセス権とは

一応アクセス権をサラッと説明しておくと、publicはどこからでも使用可能で、privateは定義したクラス内で使用可能、protectedは定義したクラスと継承先のクラスから使用可能というように使います。スコープ範囲で言うと

[広い] public > protected > private [狭い]

こんなイメージ。クラスをカプセル化したりするのに重要な役割を果たします。

privateなメンバ変数をオブジェクト外から使用

<?php
class TestPrivate {
    private $value = 'test1';
}

$a = new TestPrivate();
$a->value = 'test2';
echo $a->value;

privateなメンバ変数をクラスの外から使用します。もちろんエラーになります。

Fatal error: Cannot access private property TestPrivate::$value in test.php on line 7

privateなメンバ変数を持つクラスを継承したクラスを作成してアクセス

では、privateなメンバ変数を持つクラスを継承したクラスのしインスタンスを作成して、privateなメンバ変数を使用します。

<?php
class TestPrivate {
    private $value = 'test1';
}
class ExtPrivate extends TestPrivate {
}

$a = new ExtPrivate();
$a->value = 'test2';
echo $a->value;

これもエラーになると思うじゃないですか。実はならないんです。

test2

なぜprivateなメンバ変数がクラスの外から使用されてエラーにならないのか。

なぜエラーにならないかというと、privateは継承先で使用不可なので、継承先のクラスでは存在しないような扱いになっているようです。

ですので、継承先のクラス経由で同じ変数名に対して操作が行われた時は新たにpublicな変数が作成されてしまい、クラスの外から使用してもエラーにはなりません。

var_dumpしてクラスの中身を覗いてみると変数”value”の実体が2つあるのがわかります。

object(ExtPrivate)#1 (2) {
  ["value":"TestPrivate":private]=>
  string(5) "test1"
  ["value"]=>
  string(5) "test2"
}

protectedな変数に変更した場合

<?php
class TestPrivate {
    protected $value = 'test1';
}
class ExtPrivate extends TestPrivate {
}

$a = new ExtPrivate();
$a->value = 'test2';
echo $a->value;

継承先が有るんだからprivateではなくてprotectedにしたくなるのが人情ってもんです。$valueをprotectedな変数にしました。

これを実行してみると。。。

PHP Fatal error:  Cannot access protected property ExtPrivate::$value in test.php on line 9

エラーになります。

protectedは継承先で使用可能なため、継承先で同じ変数名を使用した場合でも実体は1つで、その変数はクラスの外から使用できないためです。

object(ExtPrivate)#1 (1) {
  ["value":protected]=>
  string(5) "test1"
}

privateからprotectedの変更する際には注意しよう

privateからprotectedに変更する際は、スコープの範囲が広がるからと言って安易に変更するのはやめましょう。上記の用にエラーを発生させてしまう可能性があります。

まあ、まず同じ変数名でprivateとpublicの実体が2つあるような使い方はまず避けるべき。

だと思います。

今回の症状はPHP5.6で確認。他のバージョンではどうなるか確認してない。知っている人には当たり前の話なのかもしれない。

コメントを残す