メンバのアクセス制限

まず、“メンバ”っていうのはオブジェクトの「メンバ変数(プロパティ)」と「メンバ関数(メソッド)」の事です。

PHP4ではオブジェクトのメンバは常に参照可能でした。が、PHP5では参照可能な範囲(可視性)を指定し、メンバは指定された範囲内でのみ参照でき、指定された範囲外から参照しようとした場合にエラーを発生します。

PHP4の場合と比較してその違いを理解して下さい。

PHP4の場合

PHP4の場合のサンプルを以下に。

<pre>
<?php
    class ANIMAL{
        var $first_weight;
        var $weight;
        var $word = "...";
        var $soul = true;
        function ANIMAL($weight){
            $this->first_weight = $this->weight = $weight;
            echo "[born! weight is {$weight}kg.]\n";
        }
        function action(){
            $this->soul() and $this->weight *= 0.9;
            $this->weight < $this->first_weight * 0.6
                and $this->soul(true);
            if($this->soul()){
                $this->cry();
                return true;
            }else{
                echo "I died...\n";
                return false;
            }
        }
        function cry(){
            echo $this->word, "\n";
        }
        function soul($death = false){
            if($death)
                $this->soul = false;
            return $this->soul;
        }
        function get_weight(){
            return $this->weight;
        }
    }
    class CAT extends ANIMAL{
        var $word = "nyan";
        function CAT($weight){
            $this->ANIMAL($weight);
        }
    }
    
    #オブジェクトを生成
    $tama = new CAT(10);
    
    #タマの人生?(死ぬまで活動する)
    while($tama->action())
        continue;
    
    #体重測定
    echo "\nweight is ", $tama->get_weight(), "kg.\n";
    
    #体重操作(変な操作)
    $tama->weight = 200;
    
    #体重測定
    echo "\nweight is ", $tama->get_weight(), "kg.\n";
?>
</pre>
[born! weight is 10kg.]
nyan
nyan
nyan
nyan
I died...

weight is 5.9049kg.

weight is 200kg.

サンプルの「ANIMAL」クラスは、活動する(「action()」メソッド)ごとに体重(「プロパティ$weight」)が減っていき、生まれた時の体重の6割を切ると力尽きてしまうという構造で、「get_weight()」メソッドで体重を得ています。そしてこの「ANIMAL」クラスを継承した「CAT」クラスを定義しています。

で、サンプルコード中に不自然な所があります。「$tama->weight = 200;」の部分です。体重を勝手に操作しちゃってます。これはいけません、問題です。体重は勝手に操作できるものではありません。まぁ、わざわざ勝手に不自然な操作をしなければいいのですが、操作できてしまうのは問題です。何が問題なのか?今回の様な簡素なものなら見てすぐ解かりますが、規模が大きくなって複雑になると、それが操作していいものなのか、何処で使うために作ったのか不明になってしまうこともあるかも知れません。この不便をPHP5では解決する事が出来ます。

PHP5の場合

アクセス制限修飾子(「public」、「protected」、「private」)

PHP5では、メンバの定義時に「アクセス制限修飾子」というものを指定することで、そのメンバが参照できる範囲(可視性)を限定する事が出来ます。この「アクセス制限修飾子」には以下の3つがあります。

アクセス制限修飾子
種類意味
public何処からでも(オブジェクト経由で)アクセス可能
protected同じクラスと派生クラス内のメソッドからのみアクセス可能
private同じクラス内のメソッドからのみアクセス可能
(PHP4式※)public」として扱われる

※.ここで言う“PHP4式”は、メンバ変数の定義時に「var」を指定し、メンバ関数の定義時には何も指定しない場合の事です。

指定された範囲外でメンバにアクセスしようとすると、エラー(「E_ERROR」)を発します。が、メンバ変数にメンバ関数から参照しようとした場合は、「未定義のプロパティを参照しようとしている」という「E_NOTICE」を発します。

メンバ変数へ不適切にアクセスした場合の動作
アクセス方法エラーの種類
メンバ内でのアクセスE_NOTICE」(未定義のプロパティへのアクセスとみなす)
オブジェクト経由のアクセスE_ERROR」(不適切なアクセス)

以下にサンプルと結果を。

<pre>
<?php
  class BASE{
    #PHP4式(PHP5では「E_STRICT」注意を発する)
    var       $var_var       = "var_property";
    #PHP5式
    public    $public_var    = "public_property";
    protected $protected_var = "protected_property";
    private   $private_var   = "private_property";
    function BASE(){
      echo "****** begin BASE constructor ******\n";
      echo '$this->var_var            : ', $this->var_var, "\n";
      echo '$this->public_var         : ', $this->public_var, "\n";
      echo '$this->protected_var      : ', $this->protected_var, "\n";
      echo '$this->private_var        : ', $this->private_var, "\n";
      echo '$this->public_method()    : ', $this->public_method(), "\n";
      echo '$this->protected_method() : ', $this->protected_method(), "\n";
      echo '$this->private_method()   : ', $this->private_method(), "\n";
      echo "****** end BASE constructor ******\n\n";
    }
    public function public_method(){
      return "public_method()";
    }
    protected function protected_method(){
      return "protected_method()";
    }
    private function private_method(){
      return "private_method()";
    }
    private function private_method_test(){
      return "private_method_test()";
    }
  }
  
  class SUB extends BASE{
    function SUB(){
      echo "\n\n****** begin SUB constructor ******\n";
      echo '$this->var_var               : ', $this->var_var, "\n";
      echo '$this->public_var            : ', $this->public_var, "\n";
      echo '$this->protected_var         : ', $this->protected_var, "\n";
      echo '$this->private_var           : ', $this->private_var, "\n";
      echo '$this->public_method()       : ', $this->public_method(), "\n";
      echo '$this->protected_method()    : ', $this->protected_method(), "\n";
      echo '$this->private_method()      : '/*, $this->private_method()*/,"\n";
      echo '$this->private_method_test() : ', $this->private_method_test(),"\n";
      echo "****** end SUB constructor ******\n\n";
    }
    #プライベートなメソッドですがオーバーライドすると・・・
    private function private_method_test(){
      return "overridden private_method_test()";
    }
  }
  
  class HODE{
    #プライベートなコンストラクタは・・・
    private function HODE(){
      echo "****** HODE constructor ******";
    }
  }
  
  
  
  #「BASE」オブジェクトを生成
  #コンストラクタがコールされ、その中で色々アクセス
  $base = new BASE;
  
  #オブジェクト経由でアクセス
  echo '$base->var_var            : ', $base->var_var, "\n";
  echo '$base->public_var         : ', $base->public_var, "\n";
  echo '$base->protected_var      : '/*, $base->protected_var*/, "\n";
  echo '$base->private_var        : '/*, $base->private_var*/, "\n";
  echo '$base->public_method()    : ', $base->public_method(), "\n";
  echo '$base->protected_method() : '/*, $base->protected_method()*/, "\n";
  echo '$base->private_method()   : '/*, $base->private_method()*/, "\n";
  
  
  #「SUB」(「BASE」の派生クラス)オブジェクトを生成
  #コンストラクタがコールされ、その中で色々アクセス
  $sub = new SUB;
  
  #オブジェクト経由でアクセス
  echo '$sub->var_var            : ', $sub->var_var, "\n";
  echo '$sub->public_var         : ', $sub->public_var, "\n";
  echo '$sub->protected_var      : '/*, $sub->protected_var*/, "\n";
  echo '$sub->private_var        : '/*, $sub->private_var*/, "\n";
  echo '$sub->public_method()    : ', $sub->public_method(), "\n";
  echo '$sub->protected_method() : '/*, $sub->protected_method()*/, "\n";
  echo '$sub->private_method()   : '/*, $sub->private_method()*/, "\n";
  
  
  #「HODE」オブジェクトを生成
  #コンストラクタがコールされます。
  new HODE;
?>
</pre>
****** begin BASE constructor ******
$this->var_var            : var_property
$this->public_var         : public_property
$this->protected_var      : protected_property
$this->private_var        : private_property
$this->public_method()    : public_method()
$this->protected_method() : protected_method()
$this->private_method()   : private_method()
****** end BASE constructor ******

$base->var_var            : var_property
$base->public_var         : public_property
$base->protected_var      : (Fatal error)
$base->private_var        : (Fatal error)
$base->public_method()    : public_method()
$base->protected_method() : (Fatal error)
$base->private_method()   : (Fatal error)


****** begin SUB constructor ******
$this->var_var               : var_property
$this->public_var            : public_property
$this->protected_var         : protected_property
$this->private_var           : (Notice:「SUB::$private_var」が未定義)
$this->public_method()       : public_method()
$this->protected_method()    : protected_method()
$this->private_method()      : (Fatal error)
$this->private_method_test() : overridden private_method_test()
****** end SUB constructor ******

$sub->var_var            : var_property
$sub->public_var         : public_property
$sub->protected_var      : (Fatal error)
$sub->private_var        : (Fatal error)
$sub->public_method()    : public_method()
$sub->protected_method() : (Fatal error)
$sub->private_method()   : (Fatal error)

(Fatal error:「HODE::HODE()」はプライベートですから云々)

E_ERROR」エラーが発生しちゃうと終了してしまうので、上のサンプルではエラーが発生するところはコメントアウトしています。要するにコメントアウトしている所でエラーが発生するって事です。

$base = new BASE」でオブジェクトを生成しています。オブジェクト生成時にコンストラクタ(勝手にコールされるメンバ関数)がコールされ、コンストラクタ内では全メンバにアクセス出来ています。

次に、生成した「オブジェクト$base」経由で各メンバにアクセスを試みますが、アクセスが許されたのは「public」宣言したもののみ(あと「var」宣言したもの)です。

同様に、「$sub = new SUB」でオブジェクトを生成し、コンストラクタがコールされます。が、基底クラスで「private」宣言した「プロパティ$this->private_var」は「未定義のプロパティである」と見なされ、「E_NOTICE」通知が発生しました。同じく基底クラスで「private」宣言した「$this->private_method()」メソッドをコールしようとすると「E_ERROR」エラーを発してスクリプトは終了してしまいます。

あと、基底クラスで「private」宣言した「$this->private_method_test()」メソッドですが、オーバーライドしたらアクセス出来るようになりました(当たり前田のナックルボールですが)。

次に、生成した「オブジェクト$sub」経由で各メンバにアクセスを試みますが、アクセスが許されたのは又しても「public」宣言したもののみ(あと「var」宣言したもの)です。

最後に「new HODE」でオブジェクトの生成を試みますが、「HODE::HODE()(コンストラクタ)はプライデートですから!残念!」と「E_ERROR」エラーを発して切腹侍です。

そういえば、「BASE」クラスと「SUB」クラスのコンストラクタにはアクセセス制限修飾子を付けませんでした。この場合の可視性は「パブリック」で、コンストラクタは問題なくコールされました。コンストラクタはオブジェクト生成時に自動的に、初めにコールされるってだけの、メソッドの1つなので、オブジェクトを生成する場所からアクセス可能な可視性で宣言しておく必要があります。

同名プロパティの可視性の優先順位と派生クラスでのアクセス制限修飾子設定時の注意点

派生クラスでメソッドをオーバーライドした場合、基底クラスのメソッドからアクセスする場合も派生クラスのメソッドからアクセスする場合も又オブジェクト経由でアクセスする場合も、コールされるのはオーバーライドされた方のメソッドです。が、プロパティを派生クラスで再定義し、基底クラスのメソッドがそのプロパティを参照しようとした場合、参照可能な可視性であれば、両方のプロパティが候補になります。

その場合どちらか一方を参照する事になるのですが、どちらを参照するかは基底クラス側で指定した可視性で決まります。以下の表の様になります。

同名プロパティの可視性の優先順位
基底クラスでの可視性参照されるプロパティ
protected」以上派生クラスのプロパティ
private基底クラスのプロパティ

つまり、常に同じクラス内のプロパティを参照する事を保障したい場合は「プライベート」で宣言し、派生クラスで再定義されたらそれを参照する場合には「プロテクテッド」以上の可視性を設定するという事です。

あと、クラス継承時の、可視性の設定において注意しなければならない事があります。派生クラスで基底クラスと同名のプロパティを再定義する場合も、メソッドをオーバーライドする場合も、派生クラスの可視性は基底クラスで設定したものと同じか、それよりも“視界が広い”ものに設定しなくてはなりません。要するに、例えば基底クラスで「プロテクテッド」宣言したメンバを派生クラスでは「プライベート」で宣言する事は出来ません。この場合は必ず「プロテクテッド」か「パブリック」に設定します。これを守らないと、クラスを定義した時点で「E_ERROR」エラーりんこになるので注意です。

基底クラスの可視性と派生クラスの可視性
基底クラスでの可視性派生クラスで有効な可視性
publicpublic
protectedpublic」、「protected
privatepublic」、「protected」、「private

では最後に、「PHP4の場合」のサンプルをPHP5用に書き換えてみます。そのサンプルと結果を以下に。

<pre>
<?php
    class ANIMAL{
        private   $first_weight;
        private   $weight;
        protected $word         = "...";
        private   $soul         = true;
        protected function ANIMAL($weight){
            $this->first_weight = $this->weight = $weight;
            echo "[born! weight is {$weight}kg.]\n";
        }
        public function action(){
            $this->soul() and $this->weight *= 0.9;
            $this->weight < $this->first_weight * 0.6
                and $this->soul(true);
            if($this->soul()){
                $this->cry();
                return true;
            }else{
                echo "I died...\n";
                return false;
            }
        }
        private function cry(){
            echo $this->word, "\n";
        }
        private function soul($death = false){
            if($death)
                $this->soul = false;
            return $this->soul;
        }
        public function get_weight(){
            return $this->weight;
        }
    }
    class CAT extends ANIMAL{
        protected $word = "nyan";
        public function CAT($weight){
            $this->ANIMAL($weight);
        }
    }
    
    #オブジェクトを生成
    $tama = new CAT(10);
    
    #タマの人生?(死ぬまで活動する)
    while($tama->action())
        continue;
    
    #体重測定
    echo "\nweight is ", $tama->get_weight(), "kg.\n";
    
    #体重操作(変な操作)
    $tama->weight = 200;
    
    #体重測定
    echo "\nweight is ", $tama->get_weight(), "kg.\n";
?>
</pre>
[born! weight is 10kg.]
nyan
nyan
nyan
nyan
I died...

weight is 5.9049kg.

weight is 5.9049kg.

まず、アクセス制限修飾子を付ける時のポイントですが、必要以上に可視性を広めないようにしたいので、全部「プライベート」にしておいて、必要な個所で「プロテクテッド」か「パブリック」に変更します。プロパティ(メンバ変数)に関しては、どっち道外部から直接参照するような使い方をするのは宜しくないので、選択肢は「プライベート」か「プロテクテッド」のみになります。

で、サンプルでは、「ANIMAL」クラスのプロパティは全て「ANIMAL」クラスのメンバでのみ利用するので「プライベート」に設定します。ただ「プロパティ$word」は「CAT」クラスで再定義し、それを参照させたいので「プロテクテッド」にします。これに伴って、「CAT」クラス側の「プロパティ$word」も「プロテクテッド」に変更します。

オブジェクト経由でコールする「action()」メソッドと「get_weight()」メソッド、あと「CAT」クラスのコンストラクタの可視性は「パブリック」に設定。また、「ANIMAL」クラスのコンストラクタは、「ANIMAL」クラスがオブジェクトを生成する事を前提とせず、その派生クラスがオブジェクトを生成するという仕様なので、「プロテクテッド」に設定します。その他のメソッドはそのまま「プライベート」に。

いまいち必要性を感じさせないアクセス制限修飾子ですが、スクリプトが大規模になって複雑化してきて、自分で書いたのに何のためのメソッドでどうやって使うもんだったかなぁと不明にならないとも限りません。そんな時の為に可視性を設定する事で、オブジェクトを生成してコールして使うメソッドなのが、内部でのみ使用されるのかが見て判るようになります。

作成日:2004年12月19日 最終更新日:2004年12月19日
【印刷モード風モード で表示】