[PHP] 初心者的なクラス・オブジェクトのお勉強 - その2

コンストラクタ、デストラクタ、final修飾子、オブジェクト引数のクラス指定、abstruct修飾子(抽象クラス・抽象メソッド)

 

その1の続き。。

コンストラクタ

その1ではPHP4式のコンストラクタを使っていた。 なぜならPHP5式のコンストラクタがあるって知らなかったからだ! orz

PHP5式では __construct というメンバ関数を定義するとこれがコンストラクタになる。

<?php
class BASE
{
  function BASE(){
    echo "BASE::BASE \n";
  }
  function __construct(){
    echo "BASE::__construct() \n";
  }
}
class EXTEND extends BASE
{
  function EXTEND(){
    echo "EXTEND::EXTEND \n";
  }
  function FUNC(){
    echo "EXTEND::extend2";
  }
  function __construct(){
    echo "EXTEND::__construct() \n";
  }
  function parent_constructor(){
    parent::__construct();
  }
}
$base = new BASE();
$extend = new EXTEND();
$extend->BASE();
$extend->parent_constructor();
$extend->__construct();
EXTEND::FUNC();
EXTEND::EXTEND();
?>

結果

BASE::__construct()
EXTEND::__construct()
BASE::BASE
BASE::__construct()
EXTEND::__construct()
EXTEND::extend2

Fatal error:  Non-static method
EXTEND::EXTEND() cannot be called statically in *** on line 52

あれ~?コンストラクタとして扱われないと思って EXTEND::EXTEND() をコールしたらFatal error…ということはコンストラクタとして呼ばれはしないけれど一応コンストラクタ扱いなのか。

デストラクタ

名前からしてコンストラクタの反対っぽい感じですがそのとおりでした。 オブジェクト(インスタンス)へのアクセス方法がなくなる(参照がすべて消える)ときに自動的にコールされるメソッド。

<?php
class CLS1{
  public $var = 1;
  function __destruct(){
    echo $this->var . "\n";
  }
}

class CLS2 extends CLS1{
  public $var = 2;
};
class CLS3 extends CLS1{
  public $var = 3;
};

$class1 = new CLS1;
$class1 = new CLS2;
$class2 = new CLS1;
new CLS3;
unset($class2);
?>

結果

1
3
1
2

どのタイミングでデストラクタがどうして実行されるのかを把握するべし。

<?php
class BASE{
    public $var;
    function __destruct(){
        echo $this->var . "\n";
    }
}
class EXTEND extends BASE{
    function __construct($num){
        $this->var = $num;
    }
}
new EXTEND(100);
$class1 = new EXTEND(1);
$class2 = $class1;
$class3 =&$class1;
$class4 = clone($class1);
$class5 = new EXTEND(5);
echo "-0-\n"; unset($class1);
echo "-1-\n"; unset($class2);
echo "-2-\n"; unset($class3);
echo "-3-\n"; unset($class4);
echo "-4-\n";
EXTEND::$var; // Fatal error
?>

結果

100
-0-
-1-
-2-
1
-3-
1
-4-
Fatal error:  Access to undeclared static property:
  EXTEND::$var in *** on line 24

デストラクタをオーバーロードすると基底クラス側のデストラクタは動作しない。

E_ERRORによる強制終了時にはデストラクタは実行されない…?ほでなすPHPに書いてある説明と違うな…バージョンや環境で動作が違う?…っぽいな。違うとこでやったらE_ERROR後に "5" が出力された。

final 修飾子

継承・オーバーライドを禁止する修飾子

<?php
final class FINAL_CLS{}
class CLS extends FINAL_CLS{}
?>

結果

Fatal error: Class CLS may not inherit
 from final class (FINAL_CLS) in *** on line 3

final修飾子で宣言されたクラスを継承しようとするとE_ERROR

<?php
class CLS1{
 public function func1(){}
}
class CLS2 extends CLS1{
 final public function func2(){}
}
class CLS3 extends CLS2{
}
class CLS4 extends CLS3{
 function func2(){}
}
?>

結果

Fatal error: Cannot override final method
 CLS2::func2() in *** on line 12

final修飾子で宣言されたメソッドをオーバーライドしようとするとE_ERROR

<?php
class TEST{
  final public $test;
}
?>

結果

Fatal error: Cannot declare property TEST::$test final,
the final modifier is allowed only for methods and classes
in *** on line 3

final修飾子はクラスとメソッドだけに使用可能ですよ

オブジェクト同士の比較

<?php
function test($text){
  echo "$text | ";
  $text = '
    $a = new stdClass;
    $b = new stdClass;
    $r =& $a;
  ' . "return" . $text . ";" ;
  eval($text) ? print("TRUE\n") : print("FALSE\n") ;
}

test('$a');
test('(array) $a');
test('$a == $b');
test('$a ===$b');
test('$a == clone($a)');
test('$a ===clone($a)');
test('$a ===$r');
?>

結果

$a | TRUE
(array) $a | FALSE
$a == $b | TRUE
$a ===$b | FALSE
$a == clone($a) | TRUE
$a ===clone($a) | FALSE
$a ===$r | TRUE

オブジェクト引数のクラス指定

関数の引数を特定のクラスに限定する。

<?php
class classA{}
class classBA extends classA{}
class classCBA extends classBA{}
$class_a   = new classA;
$class_ba  = new classBA;
$class_cba = new classCBA;

function p( $obj ){
  echo get_class( $obj ) . "\n";
}
function a  ( classA   $obj ){ p($obj); }
function ba ( classBA  $obj ){ p($obj); }
function cba( classCBA $obj ){ p($obj); }

a  ($class_a);
a  ($class_ba);
a  ($class_cba);
ba ($class_ba);
ba ($class_cba);
cba($class_cba);

echo"//以下E_ERROR組みの皆さん\n";
ba ($class_a);
cba($class_a);
cba($class_ba);
?>

結果

classA
classBA
classCBA
classBA
classCBA
classCBA
//以下E_ERROR組みの皆さん
Catchable fatal error: Argument 1 passed to ba() must be
 an instance of classBA, instance of classA given,
 called in *** on line 24 and defined in *** on line 13
Catchable fatal error: Argument 1 passed to cba() must be
 an instance of classCBA, instance of classA given,
 called in *** on line 25 and defined in *** on line 14
Catchable fatal error: Argument 1 passed to cba() must be
 an instance of classCBA, instance of classBA given,
 called in *** on line 26 and defined in *** on line 14

引数にclassAを指定したものは classA,BA,CBAのすべてを通した。引数にclassCBAを指定したものは classCBA のみを受け付けた(classA は B とC を持たないから通らない。 classBA は C を持たないから通らない)。

つまり指定したクラスのインスタンス and 指定したクラスを継承したクラスのインスタンス が引数として使用可能ということですな。

abstract修飾子

abstract修飾子を使うと抽象クラス・中傷メソッドを定義できる。抽象なんたらは何かということは、とりあえず置いておいて定義してみる。

ちなみに"abstract"を"abstruct"だと思っていてパースエラーがでまくり少々嵌った。。

<?php
class NormalClass{
  public function normal_method(){}
  //後ろに {処理} があるのは普通の関数
  //セミコロンつけるとパースエラーだよ
}
abstract class AbstractClass{
  //abstract修飾子をつけてクラスを定義
  //すると抽象クラスの出来上がり。

  abstract public function abstract_method($arg1, $arg2);
  //abstract修飾子をつけて関数を定義
  //関数名の後ろは (); で終了。
  //これは抽象メソッド

  private function normal_method(){}
  //普通のメソッドも定義できる。

  public function abstract_method2();
  //abstract を省略するとE_ERROR
}
?>

結果

Fatal error: Non-abstract method AbstractClass::abstract_method2()
 must contain body in *** on line 19

abstract修飾子をつけずに定義するメソッドは 普通のメソッド(Non-abstract method)なのです。 abstractを消すと「抽象メソッドじゃないんだから入れ物 (contein body : つまり {} ) 付けてね」と怒られました。

<?php
class NormalClass{
  abstract public function abstract_method();
}
?>

結果

Fatal error: Class NormalClass contains 1 abstract method
 and must therefore be declared abstract or implement the remaining methods (NormalClass::abstract_method) in *** on line 4

非抽象クラスに抽象メソッドが含まれるとE_ERROR

<?php
abstract class ABS{
  abstract function abs_func($arg1,$arg2);
  public function normal_func(){}
}
class CLS extends ABS{
  function __construct(){
    echo "CLSのインスタンスを生成しました";
  }
}
new CLS;
?>

結果

Fatal error: Class CLS contains 1 abstract method
 and must therefore be declared abstract
 or implement the remaining methods (ABS::abs_func) in *** on line 10

抽象クラスABSを継承したクラスCLSのインスタンスを生成しようとしたらFatal errorが出ましたとさ。抽象クラスはインスタンスを生成することはできないからね。また、この場合では抽象クラスABSを継承したクラスCLSも抽象クラスであることがわかる…が次の例はどうだろう?

<?php
abstract class ABS{
  abstract function abs_func($arg1,$arg2);
  public function normal_func(){}
}
//さてさて抽象クラスの派生クラスを作ってみましょう
class CLS extends ABS{
  function __construct(){
    echo "CSLのインスタンスを生成しました\n";
  }
  function abs_func($arg1,$arg2){}
  function normal_func($hoge){}
}
new CLS;
new ABS;
?>

結果

CSLのインスタンスを生成しました
Fatal error: Cannot instantiate abstract class ABS in *** on line 15

前の例と違って 抽象クラスを継承したクラスのインスタンスを生成できている。継承により抽象メソッドをオーバーライドすると非抽象メソッドになる。その結果として定義したクラスに抽象メソッドが含まれなくなればそのクラスは非抽象メソッドとなり、含まれれば抽象メソッドとなる。このオーバーライドを「実装」と言うらしい。

<?php
abstract class AbsCls{}
class CLS extends AbsCls{}

new CLS;
new AbsCls;
?>

結果

Fatal error:  Cannot instantiate
 abstract class AbsCls in *** on line 6

抽象メソッドを持たない抽象クラスを継承しても抽象クラスにはなれないようで CLSのインスタンスは無事に生成できた。とはいえ抽象メソッドを持たない抽象クラスは、抽象クラスだからインスタンスを生成しようとするとE_ERROR。日本語でおk

<?php
echo "HOGEHOGE";
abstract class Abs{
  abstract function func($arg1, $arg2, $arg3);
}
class CLS1 extends Abs{
  function func($test, $test, $test){}
}
class CLS2 extends Abs{
  function func($arg1){}
}
?>

結果

Fatal error: Declaration of CLS2::func() must be
 compatible with that of Abs::func() in *** on line 11

CLS1は無事に定義できたのに、CLS2は死んだ。抽象メソッドの引数の数と同じ数の引数で実装しないとダメなのだよ。

あと HOGEHOGE が出力されてないな。。クラスの定義から先に行うのかな?

<?php
abstract class AbsCls{
  abstract function func();
}
class NormalCls extends AbsCls{
 function func(){
    parent::func();
 }
}
$class = new NormalCls;
$class->func();
?>

結果

Fatal error:  Cannot call abstract method AbsCls::func() in *** on line 7

実装により抽象メソッドは消えたのかなって思ってアクセスしてみたら一応残ってはいるみたい。とはいえE_ERRORになるので実質的に消えているようなもの…なのかな?と思ったけどいやいや違いました↓

<?php
abstract class Abs{
  abstract function func();
}
class CLS1 extends Abs{
  function func(){}
}
class CLS2 extends CLS1{
  function func($arg1){}
}
?>

結果

Fatal error: Declaration of CLS2::func()
 must be compatible with that of Abs::func() in *** on line 10

CLS1は抽象クラスではなくなったけれど、CLS1のオーバーライドには抽象メソッドの制限が残ってるんだね。。

ちなみに抽象クラスに定義したスタティックメンバへはアクセス可能だよ。

その3へ続く

Share
関連記事