@blog.justoneplanet.info

日々勉強

PHP5.3の遅延静的束縛を使ってみる

そういえば今まであんまり使わなかったけど、遅延静的束縛というものを使ってみる。

class Animal{
    public function cry(){
        print(self::_getVoice());
    }
    protected static function _getVoice(){
        return '(「・ω・)「がおー';
    }
}
class Dog extends Animal{
    protected static function _getVoice(){
        return 'bow!';
    }
}
$pochi = new Dog();
$pochi->cry();// (「・ω・)「がおー

マニュアルを参照すると以下のような記述がされている。

静的遅延束縛は直近の “非転送コール”(self:: や parent::、static:: による静的なコール、 あるいはクラス階層の中での forward_static_call() によるコール) のクラス名を保存します。 静的メソッドの場合、これは明示的に指定されたクラス (通常は :: 演算子の左側に書かれたもの) となります。静的メソッド以外の場合は、そのオブジェクトのクラスとなります。 get_called_class() 関数を使うとコール元のクラス名を文字列で取得できます。 static:: はこのクラスのスコープとなります。

selfでは直近のクラスが参照されるので、上述の例の場合はselfはAnimalクラスということになる。pochiにもっと犬らしくして欲しい時は以下のようにstaticを用いて記述する。

<?php
class Animal{
    public function cry(){
        print(static::_getVoice());
    }
    protected static function _getVoice(){
        return '(「・ω・)「がおー';
    }
}
class Dog extends Animal{
    protected static function _getVoice(){
        return 'bow!';
    }
}
$pochi = new Dog();
$pochi->cry();// bow!

親クラスから子クラスのメソッドが呼び出される感じなのでアクセス権はprotected以上でないと「Fatal error: Call to private method」となる。

Pythonでは以下のように問題なく、selfを使用することができる。

class Animal:
    def cry(self):
        print(self._getVoice())
    def _getVoice(self):
        return 'gao!'

class Dog(Animal):
    def _getVoice(self):
        return 'bow!'

pochi = Dog()
pochi.cry()# bow!

Javaの場合はそもそもselfがない。クラス名を直接指定する必要があり、明示しない時はカレントクラスのメソッドがコールされる。

static class Animal{
    public static String cry(){
        return getVoice();// 明示しない場合はAnimal.となる
    }
    protected static String getVoice(){
        return "(「・ω・)「がおー";
    }
}
static class Dog extends Animal{
    protected static String getVoice(){
        return "bow!";
    }
}
Dog pochi = new Dog();
Log.e("cry", pochi.cry());// (「・ω・)「がおー

PHPのマジックメソッド

ヒョッコリまとめておくことにした

■__constructと__destruct

まぁ、説明するまでもないヤツ( ̄△ ̄;)エッ・・?

<?php
class Dog
{
    private $_name;
    public function __construct($name)
    {
        $this->_name = $name;
    }
    public function __destruct()
    {
        echo "{$this->_name}, Bye!";
    }
}
$pochi = new Dog('pochi');
unset($pochi);// pochi, Bye!
print('end...');

まぁ、こんな感じ。実用性のあるコードを書くとしたら以下のようなコードが良いかもしれない。

class Db
{
    private $dbh;
    public function __construct()
    {
        $this->_dbh = $this->_connect();
    }
    public function __destruct()
    {
        unset($this->_dbh);
    }
}

これにより接続をどこかでunsetすればリソースが早く開放できる。

■__setと__get

これらは明示されていないプロパティへの操作がトリガとなる。

class Dog
{
    private $_name;
    public function __construct($name)
    {
        $this->_name = $name;
    }
    public function __set($name, $value)
    {
        throw new Exception("Don't tell me that {$name} is {$value}!");
    }
    public function __get($name)
    {
        throw new Exception("Don't ask me about {$name}!");
    }
}
$pochi = new Dog('pochi');
try{
    $pochi->age = 25;
}
catch(Exception $e){
    print($e->getMessage());// Don't tell me that age is 25!
}
try{
    print($pochi->age);
}
catch(Exception $e){
    print($e->getMessage());// Don't ask me about gender!
}

まぁ、こんな感じだね。

■__issetと__unset

プロパティに対してのisset()やunset()がトリガとなる。

class Dog
{
    private $_name;
    public function __construct($name)
    {
        $this->_name = $name;
    }
    public function __isset($name)
    {
        throw new Exception('__isset');
    }
    public function __unset($name)
    {
        throw new Exception('__unset');
    }
}

$pochi = new Dog('pochi');
try{
    if(isset($pochi->age)){
        // something
    }
}
catch(Exception $e){
    print($e->getMessage());// __isset
}
try{
    unset($pochi->age);
}
catch(Exception $e){
    print($e->getMessage());// __unset
}

■__callと__callStatic

定義されていないインスタンスメソッドやクラスメソッドの呼び出しがトリガとなる。通常、定義されていないメソッドをコールするとエラーが発生する一方で、この2つのメソッドを定義するとエラーが発生しなくなる。従って何らかの処理を挟んだ後、該当しない場合は例外を発生させたほうが良い。

class Dog
{
    private $_name;
    public function __construct($name)
    {
        $this->_name = $name;
    }
    public function __call($method, $args)
    {
        throw new Exception('__call');
    }
    public static function __callStatic($method, $args)
    {
        throw new Exception('__callStatic');
    }
}

$pochi = new Dog('pochi');
try{
    $pochi->cry('bow!');
}
catch(Exception $e){
    print($e->getMessage());// __call
}
try{
    Dog::getData();
}
catch(Exception $e){
    print($e->getMessage());// __callStatic
}

■__sleepと__wakeup

オブジェクトがserializeやunserializeがトリガとなる。通常のクラスでは有用性があまり分からないが、リソースがプロパティに含まれている場合、serializeはリソース型をシリアライズできない。従って、__sleepと__wakeupを実装すると良いかもしれない。

class Db
{
    private $_host   = 'localhost';
    private $_dbname = 'db';
    private $_dbuser = 'user';
    private $_dbpass = 'pass';
    private $_dbh;
    public function __construct()
    {
        $this->_connect();
    }
    public function __sleep()
    {
        return array('_host', '_dbname', '_dbuser', '_dbpass');
    }
    public function __wakeup()
    {
        $this->_connect();
    }
    public function getConn()
    {
        return $this->_dbh;
    }
    private function _connect()
    {
        try {
            $this->_dbh = new PDO("mysql:host={$this->_host};dbname={$this->_dbname}", $this->_dbuser, $this->_dbpass);
            $this->_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->_dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
        }
        catch(PDOException $e){
            print($e->getMessage());
        }
    }
}
$db = new Db();
var_dump($db->getConn());// object(PDO)#2 (0) { }
$val = serialize($db);
$db = unserialize($val);
var_dump($db->getConn());// object(PDO)#4 (0) { }

ちなみに__sleepと__wakeupを記述せずに実行すると以下のようなエラーがFatalが発生する。

class Db
{
    private $_host   = 'localhost';
    private $_dbname = 'db';
    private $_dbuser = 'user';
    private $_dbpass = 'pass';
    private $_dbh;
    public function __construct()
    {
        $this->_connect();
    }
    public function getConn()
    {
        return $this->_dbh;
    }
    private function _connect()
    {
        try {
            $this->_dbh = new PDO("mysql:host={$this->_host};dbname={$this->_dbname}", $this->_dbuser, $this->_dbpass);
            $this->_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $this->_dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
        }
        catch(PDOException $e){
            print($e->getMessage());
        }
    }
}
$db = new Db();
var_dump($db->getConn());// object(PDO)#2 (0) { } 
$val = serialize($db);// Fatal error: Uncaught exception 'PDOException' with message 'You cannot serialize or unserialize PDO instances'
$db = unserialize($val);
var_dump($db->getConn());

■__invoke

こいつを使うとコールバック関数とかが気持ちよく書ける。

class Group
{
    public function __invoke($params)
    {
        return $params . ', bow!';
    }
}
$group = new Group();
$result = array_map($group, array('Pochi', 'Shiro'));
var_dump($result);
/*
array(2) {
  [0]=>
  string(11) "Pochi, bow!"
  [1]=>
  string(11) "Shiro, bow!"
}
*/

今までPHPのコールバック関数が気持ち悪くて書きたくなかったが改心しました。

PHP Reflection API

PHP5には完全なリフレクションAPIが付属していて、クラス、インターフェイス、関数、メソッドなどについて、リバースエンジニアリングを行うことができる。

■定義済みクラスについて調べる

定義済みクラスについて調べるには、以下のようにReflectionClassクラスを使う。

<?php
/**
 * Dog
 */
class Dog {
    private $name;
    public function __construct($name){
        $this->name = $name;
    }
    /**
     * bark
     * @param
     * @return
     */
    public function bark(){
        print('Bow!');
    }
}
$class = new ReflectionClass('Dog');
print($class->getName());//Dog
print($class->getDocComment());
/**
 * Dog
 */
print($class->getFileName());
print($class->getStartLine());

$methods = $class->getMethods();
var_dump($methods);
/*
array(2) {
  [0]=>
  &object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(11) "__construct"
    ["class"]=>
    string(3) "Dog"
  }
  [1]=>
  &object(ReflectionMethod)#3 (2) {
    ["name"]=>
    string(4) "bark"
    ["class"]=>
    string(3) "Dog"
  }
}
*/
$tmpAry = array();
foreach($methods as $method){
    $tmpAry[] = array(
        'name' => $method->name,
        'param' => $method->getParameters()
    );
}
/*
array(2) {
  [0]=>
  array(2) {
    ["name"]=>
    string(11) "__construct"
    ["param"]=>
    array(1) {
      [0]=>
      &object(ReflectionParameter)#4 (1) {
        ["name"]=>
        string(4) "name"
      }
    }
  }
  [1]=>
  array(2) {
    ["name"]=>
    string(4) "bark"
    ["param"]=>
    array(0) {
    }
  }
}
*/
?>

ReflectionClassのメソッドについて

string ReflectionClass::getName(void)
クラス名を返す。
string ReflectionClass::getDocComment(void)
ドキュメントのコメントを返す。
string ReflectionClass::getFileName
ファイル名を返す。
int ReflectionClass::getStartLine
クラスを宣言してる行番号を返す。
array ReflectionClass::getProperties
プロパティをReflectionPropertyクラスのオブジェクトの配列として返す。
array ReflectionClass::getMethods
メソッドをReflectionMethodクラスのオブジェクトの配列として返す。
string ReflectionClass::getParentClass
親クラスの名前を返す。

■定義済みクラスを調べる

以下のようにget_declared_classes関数を使って、定義済みクラス名を列挙することができる。但し、この関数は後述のget_defined_functions関数と異なり、システム定義とユーザ定義を明確に区別しない。

<?php
class Dog {
	//code
}
var_dump(get_declared_classes());
/*
array(101) {
  [0]=>
  string(8) "stdClass"
  [1]=>
  string(9) "Exception"
  [2]=>
  string(14) "ErrorException"
・・・
  [44]=>
  string(10) "Reflection"
  [45]=>
  string(18) "ReflectionFunction"
  [46]=>
  string(19) "ReflectionParameter"
  [47]=>
  string(16) "ReflectionMethod"
  [48]=>
  string(15) "ReflectionClass"
  [49]=>
  string(16) "ReflectionObject"
  [50]=>
  string(18) "ReflectionProperty"
  [51]=>
  string(19) "ReflectionExtension"
・・・
  [89]=>
  string(12) "PDOException"
  [90]=>
  string(3) "PDO"
  [91]=>
  string(12) "PDOStatement"
・・・
  [100]=>
  string(3) "Dog"
}
*/
?>
array get_declared_classes(void)
宣言済みのクラス名の配列を返す。

■定義済み関数について調べる

定義済み関数を調べるには、以下のようにReflectionFunctionクラスを使う。

<?php
function sample($to = 'world'){
    print('hello, {$to}');
}
$funcs = get_defined_functions();
foreach($funcs['user'] as $key => $value){
	try{
		$func = new ReflectionFunction($value);
	}
	catch(ReflectionException $e){
		//code
	}
	var_dump($func->name);
	var_dump($func->getParameters());
}
/*
string(6) "sample"
array(1) {
  [0]=>
  &object(ReflectionParameter)#2 (1) {
    ["name"]=>
    string(2) "to"
  }
}
*/
?>

また、ReflectionFunctionクラスには以下のように様々なメソッドが用意されている。getParametersメソッドで引数を返し、getFileNameメソッドでファイル名を返し、getStartLineメソッドで関数が記述されている行番号を返すといった感じだ。

<?php
function sample($to = 'world'){
    print('hello, {$to}');
}
function hoge($a, $b = array()){}
$funcs = get_defined_functions();
$tmpAry = array();
foreach($funcs['user'] as $key => $value){
	try{
		$func = new ReflectionFunction($value);
	}
	catch(ReflectionException $e){
		//code
	}
	$args = array();
	foreach($func->getParameters() as $param){
		$arg = '';
		if($param->isPassedByReference()){
			$arg = '&';
		}
		if($param->isOptional()){
			$arg = '[' . $param->getName() . ' = ' .$param->getDefaultValue() . ']';
		}
		else{
			$arg = $param->getName();
		}
		$args[] = $arg;
	}
	$tmpAry[] = array(
		'prototype' => $func->name,
		'file'      => $func->getFileName(),
		'line'      => $func->getStartLine(),
		'arg'       => $args
	);
}
var_dump($tmpAry);
/*
array(2) {
  [0]=>
  array(4) {
    ["prototype"]=>
    string(6) "sample"
    ["file"]=>
    string(57) "/var/www/sample.com/httpdocs/sample.php"
    ["line"]=>
    int(5)
    ["arg"]=>
    array(1) {
      [0]=>
      string(12) "[to = world]"
    }
  }
  [1]=>
  array(4) {
    ["prototype"]=>
    string(4) "hoge"
    ["file"]=>
    string(57) "/var/www/sample.com/httpdocs/sample.php"
    ["line"]=>
    int(8)
    ["arg"]=>
    array(2) {
      [0]=>
      string(1) "a"
      [1]=>
      string(11) "[b = Array]"
    }
  }
}
*/
?>

ReflectionFunctionクラスのメソッドについて

string ReflectionFunction::getName(void)
関数名を返す
string ReflectionFunction::getDocComment(void)
ドキュメントのコメントを返す
string ReflectionFunction::getFileName(void)
ファイル名を返す
int ReflectionFunction::getStartLine(void)
関数を宣言してる行番号を返す
array ReflectionFunction::getParameters(void)
関数の引数をReflectionParameterクラスのオブジェクトの配列として返す

定義済み関数の列挙

PHPでは以下のようにget_defined_functions関数を用いて、定義済み関数を列挙することができる。

<?php
function sample(){
    print('hello!');
}
var_dump(get_defined_functions());
/*
array(2) {
  ["internal"]=>
  array(1510) {
    [0]=>
    string(12) "zend_version"
    [1]=>
    string(13) "func_num_args"
    [2]=>
    string(12) "func_get_arg"
    [3]=>
    string(13) "func_get_args"
    [4]=>
    string(6) "strlen"
    [5]=>
    string(6) "strcmp"
・・・
    [1508]=>
    string(22) "ioncube_loader_version"
    [1509]=>
    string(23) "ioncube_loader_iversion"
  }
  ["user"]=>
  array(1) {
    [0]=>
    string(6) "sample"
  }
}
*/
?>

各関数について

array get_defined_functions(void)
定義済みの全ての関数の名前を配列で返す。

■まとめ

クラスについてはReflectionClassクラスにクラス名を引数として与えると、各メソッドで情報を取得できる。但し、getMethodsメソッドなどは(ReflectionMethodクラスの)オブジェクトの配列を返す。そのオブジェクトに対するメソッドgetParametersは、(ReflectionParameterクラスの)オブジェクトの配列を返す。

関数についてはReflectionFunctionクラスに関数名を引数として与えると、書くメソッドで情報を取得できる。但し、getParametersメソッドなどは(ReflectionParameterクラスの)オブジェクトの配列を返す。

PHP Lazy Loading(クラスファイルの自動読込)

PHP5以上では、いわゆる「遅延読込」を可能にする自動読込の機能が用意されている。

<?php
function __autoload($class){
    require_once(str_replace('_', '/', $class));
}
$obj = new Some_Class();
?>

■Standard PHP Library(SPL)による自動読み込み

__autoload関数は単一のディレクトリ構成ルールの元でコーディングする際には非常に役立つが、異なったライブラリを使うなどした場合に、非常に重く遅くなる。従って、以下のように記述する。

<?php
spl_autoload_register('spl_autoload');
if(function_exists('__autoload')){
    spl_autoload_register('__autoload');
}
?>

もしもユーザーが定義した__autoload関数がある場合は、条件分岐し登録してあげる必要がある。

spl_autoload関数

全てのインクルードパスから、クラス名を小文字にし「.php」や「.ini」を付加し検索する。付加する拡張子を加えたい場合は、spl_autoload_extensions関数を使用する。

spl_autoload_register

指定した関数を__autoloadのデフォルトの実装とする。

個人的には、プログラムの最初にインクルードファイルを明記するやり方が好きなのでイマイチ使ったことがないが、大規模なアプリケーションでの有用性は垣間見える。

PHP Exceptions(PHPの例外処理)

■PHPにおける「エラー」と「例外」の違い

  • 例外はオブジェクトであり、エラーが起こったときにthrowされる
  • 例外はハンドリングすることが可能、(継承させた)例外のタイプで条件分けできる
  • 例外はハンドリングされていない場合、全てfatalである
  • 例外は発生時にインスタンス化され、コンストラクタが実行される
  • 例外はコードの処理の流れを変える

■Exceptionクラス

以下のような実装になっている。但し、ビルトインクラスであり、ユーザーが記述する必要はない。

<?php
class Exception {
    protected $message = 'Unknown Exception';
    protected $code = 0;
    protected $fill;
    protected $line;
    function __construct($message = null, $code = 0);
    final function getMessage();
    final function getCode();
    final function getFile();
    final function getLine();
    final function getTrace();
    final function getTranceAsString();
    function __toString();
}
?>

Exceptionクラスは継承して効果的にユーザー仕様の例外を実装することができる。以下のようにPDOにおいても垣間見ることができる。

<?php
try {
    $dsn = 'mysql:host=localhost;dbname=dbname';
    $dbh = new PDO($dsn, DB_USER, DB_PASS);
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e){
    print('Exception: ' . $e->getMessage());
}
?>

class PDOException extends Exceptionしてる部分と、new PDOExceptionしてるコードがあるってことだな。。。

■例外の発生

通常、throw文によるエラーが起こったときに、例外はインスタンス化され発生する。

<?php
throw new Exception('an error occurs');//Fatal error: Uncaught exception 'Exception' with message 'an error occurs'
?>

例外はユーザー定義した例外や通常の例外まで伝播する。例外が発生する可能性がある場合、一般的には以下のようにtry~catch…ブロックが用いて例外をハンドリングする。

<?php
try {
    $dsn = 'mysql:host=localhost;dbname=dbname';
    $dbh = new PDO($dsn, DB_USER, DB_PASS);
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e){
    print('PDOException: ' . $e->getMessage());
}
?>

また、伝播する例外を分別してキャッチするには以下のようなコードを使う。tryをネストすることも可能だが見にくくなるので以下のような形が望ましい。

<?php
class MyException extends Exception {}
try {
    $dsn = 'mysql:host=localhost;dbname=dbname';
    $dbh = new PDO($dsn, DB_USER, DB_PASS);
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    //throw new MyException();
}
catch(MyException $e){
    print('MyException: ' . $e->getMessage());
}
catch(PDOException $e){
    print('PDOException: ' . $e->getMessage());
}
catch(Exception $e){
    print('Exception: ' . $e->getMessage());
}
?>

例外はcatchできればスクリプトの実行は止まらない。しかし、キャッチしきれないとFatal errorが起こりスクリプトの実行は止まってしまう。キャッチしきれない(ハンドリングされていない例外が発生した)ときは、以下のようにして挙動を定義することができる。但し、この場合でもスクリプトの実行が止まることに変わりはない。

<?php
function handleUncaughtException($e){
    print($e->getMessage());
}
set_exception_handler('handleUncaughtException');
throw new Exception('my error');//my error
?>

なんらかの事情で一括してハンドリングされていない例外の挙動を変更したい場合に有用なのかもしれない。

PHP Interfaces and Abstract Classes(インターフェースと抽象クラス)

抽象クラス(やインターフェース)というものは、「ここにコンナものがある」というような抽象的な雛形を用意することである。抽象的であるがゆえ、実際のモノとして生成できず(インスタンス化不可)、「コンナもの」を詳しく継承先で記述してあげる必要がある。

■インターフェース

  • サブクラスに実装を強制することができる
  • 実装を強制させるメソッド以外は何も記述できない(抽象クラスとは異なる点)
  • インスタンスを生成することはできない
  • 複数のインターフェースを実装できる(抽象クラスとは異なる点)
<?php
interface Dog {
    public function bark();
    public function appeal($thing);
}
class Pochi implements Dog {
    public function bark(){
        print('Bow!');
    }
    public function appeal($thing){
        print($thing);
    }
}
$pochi = new Pochi();
$pochi->bark();//Bow!
$pochi->appeal('Give me feeds!!');//Give me feeds!!
?>

インターフェース間での継承

以下のようにインターフェースAnimalをインターフェースDogが継承することができる。その際、以下のようにキーワードextendsを用いる。

<?php
interface Animal {
    public function appeal($thing);
}
interface Dog extends Animal {
    public function bark();
}
class Pochi implements Dog {
    public function bark(){
        print('Bow!');
    }
    public function appeal($thing){
        print($thing);
    }
}
$pochi = new Pochi();
$pochi->bark();//Bow!
$pochi->appeal('Give me feeds!!');
?>

複数のインターフェースの実装

以下のようにカンマで区切って複数のインターフェースを記述する。但し、メソッド名に重複があるとエラーになる。

<?php
interface Human {
    public function walk();
}
interface Dog {
    public function bark();
}
class Pochi implements Human,Dog {
    public function bark(){
        print('Bow!');
    }
    public function walk(){
        print('Walking!');
    }
}
$pochi = new Pochi();
$pochi->bark();//Bow!
$pochi->walk();//Walking!
?>

■抽象クラス

  • サブクラスに実装を強制することができる
  • 抽象メソッドを1つ以上持つクラスは、抽象クラスとしてabstractをつけて宣言しなくてはならない
  • インスタンスを生成することはできない
  • 仕様上、抽象メソッドにデフォルトの処理を記述することはできない
  • 抽象クラス内の通常メソッドに、継承させるべく処理を記述することができる(インターフェースとは異なる点)

以下のように、キーワード「abstract」を用いて、抽象クラス、メソッドを記述できる。

<?php
abstract class Dog {
    protected $name;
    abstract public function bark();
    public function __construct($name){
        $this->name = $name;
    }
}
class Pochi extends Dog {
    public function bark(){
        print("My name is {$this->name}. Bow!");
    }
}
$pochi = new Pochi('pochi');
$pochi->bark();//My name is pochi. Bow!
?>

但し、以下のように記述するとシンタックスエラーとなる。つまり、抽象プロパティというものは存在できない。

<?php
abstract class Dog {
    abstract protected $name;//syntax error!!
    abstract public function bark();
    public function __construct($name){
        $this->name = $name;
    }
}
?>

「抽象クラス」と「インターフェース」の同時利用

また、以下のように「抽象クラスの継承」と「インターフェースの実装」を、1つのクラスで同時に行うことができる。

<?php
abstract class Animal {
    abstract public function walk();
}
interface Dog {
    public function bark();
}
class Pochi extends Animal implements Dog {
    public function bark(){
        print('Bow!');
    }
    public function walk(){
        print('Walking!');
    }
}
$pochi = new Pochi();
$pochi->bark();//Bow!
$pochi->walk();//Walking!
?>

予備知識

PHPでは多重継承はサポートされていない。多重継承がサポートされている言語は、PythonやC++などだけである。

PHP Class Constants and Static Members(クラス定数と静的メンバ)

■静的なメソッドとプロパティ

PHP5では、以下のようにstaticキーワードを使って静的なメソッドとプロパティを定義できる。

<?php
class Dog {
    public static function bark(){
        print('bow!');
    }
}
Dog::bark();//bow!
?>

staticで宣言したメソッドやプロパティは個々のオブジェクトでなくクラスに属する。クラス内外から呼び出す際には、スコープ演算子「::」を用いて「クラス名::メンバ名」という記述をする。この時に演算子「->」を使用するのは文法上誤りである。また、自身のクラス内から静的メンバを呼び出すには、以下のようにキーワード「self」を用いる。

<?php
class Dog {
    public static function bark(){
        print('bow!');
    }
    public static function appeal($thing){
        print("$thing");
        self::bark();
    }    
}
Dog::appeal('Give me feeds!');//Give me feeds!bow!
?>

クラス設計について

staticなメンバだけのクラスを設計することもできる。

  • オブジェクト指向の本来の意義からかけ離れていないか注意
  • 機能のパッケージングという点から見ると有効

■クラス定数

  • クラス定義時に値が決められ、後から変更はできない
  • クラス内部で使用する定数値を、名前でスコープ外に公開することを目的とする
  • スカラー値限定

クラス定数を定義するには以下のようにキーワードconstを用いる。また、クラス外部からアクセスするには、スコープ演算子「::」を用いる。

<?php
class Dog {
    const VOICE = 'Bow!';
}
print(Dog::VOICE);//Bow!
?>

以下のように、PDOのエラー表示などの設定でも使われているようだ。これによって上手にパッケージ化することができている。

<?php
try{
    $dsn = 'mysql:host=localhost;dbname=dbname';
    $dbh = new PDO($dsn, DB_USER, DB_PASS);
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e){
    //code
}
?>

PHP Class Methods and Properties(メソッドとプロパティ)

■インスタンスメソッド

インスタンスメソッドは通常の関数と同じく以下のように宣言できる。また、インスタンスメソッドを呼び出すには、「インスタンス名->メソッド名」というように演算子->を用いる。

class Dog {
    public function bark(){
        print('bow!');
    }
}
$pochi = new Dog();
$pochi->bark();//bow!

$this

一方で、クラス内からメンバ関数を呼び出すには、以下のように$thisを用いる(静的メソッドの場合はキーワードselfを用いる)。

class Dog {
    public function bark(){
        print('bow!');
    }
    public function appeal($thing){
        $this->bark();
        print("Give me {$thing}!");
    }
}
$pochi = new Dog();
$pochi->appeal('feeds');//bow!Give me feeds!

コンストラクタ

特別なクラスメソッドの一種でクラスがnew演算子でインスタンス化されるときに呼び出される関数である。

PHP5
class PersonClass {
    function __construct($name){
        print($name);
    }
}
$john = new PersonClass('John');
PHP5 & PHP4ハイブリッド

PHP4ではクラス名と同じメソッド名がコンストラクタとして扱われていた。しかしこの方法には大きな欠点があり、クラス名を変える場合にコンストラクタ名まで変更しなくてはならなかった。そこでPHP5では、関数__constructがコンストラクタとして扱われるようになった。但し、関数__constructが存在しない場合は、インタプリタはPHP4形式のコンストラクタを探すようになっている。

class PersonClass {
    function __construct($name){
        print($name);
    }
    function PersonClass($name){
        print($name);
    }
}
$john = new PersonClass('John');
予備知識

PHPではコンストラクタの連鎖呼び出しは行われない。サブクラスのインスタンス作成時には、サブクラスのコンストラクタしか実行されない。

class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function introduce(){
        print("I'm {$this->name}! Nice to meet you!");
    }
}
class Dog extends Animal{
    public function __construct($name){
        //no code
    }
    public function introduce(){
        parent::introduce();
        print(' bow!');
    }
    public function bark(){
        print("{$this->name}, bow!");
    }
}
$pochi = new Dog('Pochi');
$pochi->introduce();//I'm ! Nice to meet you! bow!
$pochi->bark();//, bow!

但し、以下のようにサブクラスでコンストラクタを宣言しない場合は、当然だがコンストラクタも継承されている。

class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function introduce(){
        print("I'm {$this->name}! Nice to meet you!");
    }
}
class Dog extends Animal{
    public function introduce(){
        parent::introduce();
        print(' bow!');
    }
    public function bark(){
        print("{$this->name}, bow!");
    }
}
$pochi = new Dog('Pochi');
$pochi->introduce();//I'm Pochi! Nice to meet you! bow!
$pochi->bark();//Pochi, bow!

コンストラクタに何らかの処理を加えたい場合、以下のようにサブクラスでコンストラクタを宣言(上書き)し、スーパークラスのコンストラクタを呼び出した後に追記する。

class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function introduce(){
        print("I'm {$this->name}! Nice to meet you!");
    }
}
class Dog extends Animal {
    public function __construct($name){
        parent::__construct($name);
        print("Hello, My name is {$this->name}!");
    }
    public function introduce(){
        parent::introduce();
        print(' bow!');
    }
    public function bark(){
        print("{$this->name}, bow!");
    }
}
$pochi = new Dog('Pochi');//Hello, My name is Pochi!
$pochi->introduce();//I'm Pochi! Nice to meet you! bow!
$pochi->bark();//Pochi, bow!

デストラクタ

PHP5では、オブジェクトの最後の参照が破棄されると、関数__destructが呼び出される。__destructには引数を設定できない。

class PersonClass {
    function __construct($name){
        print(__METHOD__);
    }
    function __destruct(){
        print(__METHOD__);
    }
}
$john = new PersonClass('John');
$terminator = $john;
print('Copied!');
unset($john);
print('T1000 killed John');
//PersonClass::__construct
//Copied!
//T1000 killed John
//PersonClass::__destruct

$johnがunsetされた段階では、$terminatorの参照が残っているためにデストラクタは呼び出されない。リソースの開放などには非常に有用である。

■マジックメソッド

例えば、以下のように存在しないプロパティにアクセスしようとしても、何のエラーも出ない。また、雛型には存在しないようなプロパティが作られてしまう。

class Dog {
    public function bark(){
        print('Bow!');
    }
}
$pochi = new Dog();
$pochi->name = 'Pochi';
print($pochi->name);//Pochi

そのようなときに有用なのが__setメソッドと__getメソッドだ。以下のようにするとメッセージを表示するようにできる。また、コードを修正しエラーを発生させることもできる。

class Dog {
    public function bark(){
        print('Bow!');
    }
    public function __set($prop, $val){
        print('Error:set');
    }
    public function __get($prop){
        print('Error:get');
    }
}
$pochi = new Dog();
$pochi->name = 'Pochi';//Error:set
print($pochi->name);//Error:get

各メソッドについて

mixed __get(mixed $name)
存在しないプロパティの値を参照しようとしたときに呼び出される。但し、引数は必ず1つ記述しなければならない。
void __set(string $name, mixed $value)
存在しないプロパティに値を代入しようとしたときに呼び出される。但し、引数は必ず2つ記述しなければならない。
mixed __call(string $name, array $arguments)
存在しなかったメソッドが呼び出されたときに呼び出される。但し、デフォルトではFatal Errorが発生するので、実装しなくても安全である。
void __clone(void)
clone演算子が使用されたときの挙動を定義できる。
void __sleep(void)
serialize関数が呼び出されたときに、シリアル化の前に呼び出される。
void __wakeup(void)
unserialize関数が呼び出されたときに呼び出される。
bool __isset(string $name)
未定義のプロパティに対してissetが使用されたときに実行される。
void __unset(string $name)
未定義のプロパティにunsetが使用されたときに実行される。
mixed __toString(void)
クラスがprint関数などで文字列に変換される際の挙動を定義できる。
mixed __set_state(array $propaty_pairs)
var_export関数によって エクスポートされたクラスのためにコールされる。

■プロパティ

プロパティの宣言は必須ではないが、コードの保守性、可読性の向上のために明示することを強く勧める。

class Dog {
    private $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function bark(){
        print($this->name . ', bow!');
    }
    public function appeal($thing){
        $this->bark();
        print($this->name . ", Give me {$thing}!");
    }
}
$pochi = new Dog('Pochi');
$pochi->bark();//Pochi, bow!
$pochi->appeal('feeds');//Pochi, bow!Pochi, Give me feeds!

以下のようにプロパティを宣言しなくても、特にエラーではないがメンテナンスがしづらい。

class Dog {
    public function __construct($name){
        $this->name = $name;
    }
    public function bark(){
        print($this->name . ', bow!');
    }
    public function appeal($thing){
        $this->bark();
        print($this->name . ", Give me {$thing}!");
    }
}
$pochi = new Dog('Pochi');
$pochi->bark();//Pochi, bow!
$pochi->appeal('feeds');//Pochi, bow!Pochi, Give me feeds!
print($pochi->name);//Pochi

但し、最終ラインを見ても分かるように、プロパティは自動的にpublicとなってしまう。

プロパティのデフォルト値

以下のようにしてプロパティにデフォルト値を設定することができる。但し、デフォルト値として設定ができるのは、評価を必要としない単純な定数値や配列だけである。

class MyClass {
    private $str = 'sample';
    private $num = 123456789;
    private $ary = array(1, 2, 3);
    public function getStr(){
        return $this->str;
    }
}
$obj = new MyClass();
print($obj->getStr());//sample

また、一般的にはプロパティのアクセスコントロールはprivateやprotectedにしておく。外からアクセスする必要がある場合のみ、上述のgetStr関数のような「アクセサメソッド」を準備してアクセスできるようにする(情報の隠蔽性による判断)。

PHP Class Inheritance and Member Visibility(継承と可視性)

■可視性

PHP5ではメンバの可視性を、以下のキーワードを使用して設定することができる。明示しない場合はpublicとなるが、個人的には可読性を上げる意味も含め全てのメンバにアクセス修飾子を記述するべきかと思う。

public 全てのスコープからアクセスができる
protected サブクラスからアクセスができる
private 自身のクラス内のみアクセスできる
final 全てのスコープからアクセスができる。但し、上書きができない

名前空間が近いかもしれないが、Javaと違ってパッケージという概念はない。

クラスにfinalを設定する場合

finalキーワードはクラスの継承を防ぐ事もできる。以下のように記述するとエラーとなる。

final class MyClass {
    //code
}
class ChildClass extends MyClass  {
    //code
}

■クラスの継承

オブジェクト指向では継承という概念の元、親クラスの機能をそのまま受け継いだり補完したりして、コードの記述量を減らしたり機能の拡張を行ったりすることができる。また、親クラスのメソッドを呼び出したいときは「parent::メソッド名」の記法を用いる。

class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function introduce(){
        print("I'm {$this->name}! Nice to meet you!");
    }
}
class Dog extends Animal {
    public function introduce(){
        parent::introduce();
        print(' bow!');
    }
    public function bark(){
        print("{$this->name}, bow!");
    }
}
$pochi = new Dog('Pochi');
$pochi->introduce();//I'm Pochi! Nice to meet you! bow!
$pochi->bark();//Pochi, bow!

但し、以下のようにクラスAnimalでprivate $nameとしてしまうと、サブクラスには$nameが雛形として存在しないため(アクセスできず)、エラーが発生する。

class Animal {
    private $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function introduce(){
        print("I'm {$this->name}! Nice to meet you!");
    }
}
class Dog extends Animal {
    public function introduce(){
        parent::introduce();
        print(' bow!');
    }
    public function bark(){
        print("{$this->name}, bow!");
    }
}
$pochi = new Dog('Pochi');
$pochi->introduce();//I'm Pochi! Nice to meet you! bow!
$pochi->bark();//Notice:  Undefined property:  Dog::$name in ....

実例

どの参考書を見てもイマイチ理解しづらい。そんな時は、色々なものを書いてみることを繰り返すしかないかな・・・

通常の継承
class Animal {
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print('Bow!');
    }
}
$pochi = new Dog();
$pochi->bark();//Bow!

まぁ、こんな感じだ。でも、犬の名前くらい付けてやりたい。

プロパティの使用

そこで犬の名前を保持する方法を考える。$pochiは変数名であり、文字列として名前を保持するにはプロパティを使用する。

class Animal {
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print('Bow!');
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//Bow!

上述のようにプロパティを明記しなくてもエラーではないが、非常に分かりにくい上にアクセス権がpublicに設定されるので以下のように記述する。

class Animal {
    private $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print('Bow!');
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//Bow!
プロパティとアクセス権

上述のコードでbarkメソッドに自分の名前を含めるたいときには、ちょっと頭をひねらなければならない。以下のようにDogクラスから$thisを参照すると、スーパークラスの$name
はサブクラスからは見えないので、「Notice: Undefined property: Dog::$name」とエラーが表示される。

class Animal {
    private $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
}
$pochi = new Dog('pochi');
$pochi->bark();

そこで以下のようにアクセス修飾詞protectedを用いて、$nameを継承されつつ外からは見えないように設定する。

class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!

privateのままで、全てサブクラスでオーバーライドする方法もあるが記述量が多くなり、オブジェクト指向の恩恵が少なくなると思われる。

抽象クラスを使用した継承

但し、深く考えてみると「Animal」というクラスは、クラスの雛形的な存在でインスタンス化しない可能性が高いことに気づく。従って、以下のように抽象クラスを使用してみる。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!

これでクラスAnimalはインスタンス化できなくなった。せっかく抽象クラスにしたので抽象メソッドも実装してみたい。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
    abstract public function speak($content);
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!

当然ではあるが、サブクラスでspeakメソッドを実装しなくてはならなくなった。

アクセサメソッドの実装

何らかの都合で$nameに外側からアクセスしたい可能性があるかもしれない。publicにする方法も考えられるが、オブジェクト指向のメリットの一つである隠蔽性が低下してしまう。そこでアクセス専用のメソッドを用意してあげるのがイイと思う。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
    public function getName(){
        return $this->name;
    }
    abstract public function speak($content);
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!
print($pochi->getName());//pochi
オーバーライド

サブクラスでスーパークラスのメソッドを補完したい場合もあるかもしれない。以下のように、サブクラスでスーパークラスと同名のメソッドを再度宣言するオーバーライドといった手法で、そういった要望に応えることができる。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
    public function getName(){
        return $this->name;
    }
    abstract public function speak($content);
}
class Dog extends Animal {
    public function walk(){
        parent::walk();
        print("n{$this->name}");
    }
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->walk();//I'm walking! pochi

ちなみに、「self::」でコールする場合はメソッドはstaticである必要があるが、「parent::」でコールする場合はstaticでなくても問題ない。

オーバーライドをさせない実装

キーワード「final」を使用する。以下の例ではサブクラスでwalkメソッドを再定義することはできない。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    final public function walk(){
        print('I\'m walking!');
    }
    abstract public function speak($content);
}
class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!
$pochi->walk();//I'm pochi. Bow!

また、以下のようにクラス名の前において、クラス自身を継承させないようにすることもできる。

abstract class Animal {
    protected $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function walk(){
        print('I\'m walking!');
    }
    abstract public function speak($content);
}
final class Dog extends Animal {
    public function bark(){
        print("I'm {$this->name}. Bow!");
    }
    public function speak($content){
        print($content);
    }
}
$pochi = new Dog('pochi');
$pochi->bark();//I'm pochi. Bow!

PHP Object Oriented Programming(オブジェクト指向の基本)

オブジェクト指向は、データと処理のクラスと呼ばれるセットを軸としている。クラスにデータと処理を「まとめ」、情報を「隠し」、クラスを雛形として「沢山作る」ことが即ちオブジェクト指向といっても良いかもしれない。

■呼び名

メソッド

クラスのメンバの一種。クラスに属する関数。

プロパティ

クラスのメンバの一種。データを保持する。

■クラスの宣言

以下のようにキーワードclassを用いる。クラス名は大文字/小文字の区別がされない。

class MyClass {
    //code
}

クラスにおける慣例

  • クラス名の最初の文字は大文字
  • ファイル名はclass名[.class].php

■クラスのインスタンス化

インスタンス化は以下のようにnew演算子を使って行う。

$obj = new MyClass();

静的メソッドなどが存在する場合はインスタンス化しないことも多々ある。

$obj = MyClass::getInstance();

オブジェクトのコピー

PHP4ではオブジェクトを代入すると、クローンが生成されていた。しかし、PHP5でクローンを生成するためには、以下のようにclone演算子を用いなければ、参照が渡されるだけである。但し、個人的にclone演算子を使った事がないのもあり、イマイチ有用だとは思わない。

$obj = new MyClass();
$copy = clone $obj;

■クラスの判別

どのクラスをインスタンス化したものか判別するには、以下のように演算子「instanceof」を用いる。

class Dog {
    //code
}
$pochi = new Dog();
if($pochi instanceof Dog){
    print('$pochi is instance of Dog.');
}