※当サイトの記事には、広告・プロモーションが含まれます。

他のオブジェクト指向言語とは異なるPHPのオーバーロード

オーバーロード

プログラミング言語において関数演算子メソッドの同一名や同一の演算子記号について複数定義し、利用時にプログラムの文脈に応じて選択することで複数の動作を行わせる仕組みである。

PHPには「オーバーロード」という機能が存在するが、これはプロパティやメソッドを動的に作成するための機能であり、他の多くのオブジェクト指向言語とは異なる意味で用いられている。

 

PHPマニュアル(PHP: オーバーロード - Manual )によると

オーバーロード

PHP におけるオーバーロード機能は、 プロパティやメソッドを動的に "作成する" ための手法です。
これらの動的エンティティは、マジックメソッドを用いて処理されます。
マジックメソッドは、クラス内でさまざまなアクションに対して用意することができます。

オーバーロードメソッドが起動するのは、 宣言されていないプロパティやメソッドを操作しようとしたときです。
また、現在のスコープからは アクセス不能な プロパティやメソッドを操作しようとしたときにも起動します。
このセクションでは、これらの (宣言されていない、 あるいは現在のスコープからはアクセス不能な) プロパティやメソッドのことを "アクセス不能プロパティ" および "アクセス不能メソッド" と表記することにします。

オーバーロードメソッドは、すべて public で定義しなければなりません

注意:

これらのマジックメソッドの引数は、 参照渡し とすることはできません。

注意:

PHP における "オーバーロード" の解釈は、他の多くのオブジェクト指向言語とは異なります。
一般的に「オーバーロード」とは、 「名前は同じだけれども引数の数や型が異なるメソッドを複数用意できる」 という機能のことを指します。

変更履歴

バージョン 説明
5.3.0 __callStatic() が追加されました。
public で、かつ static でない宣言を強制するような警告が追加されました。
5.1.0 __isset()__unset() が追加されました。
__get() が private プロパティのオーバーロードに対応しました。
5.0.0 __get() が追加されました。

 

プロパティのオーバーロード

public void __set ( string $name , mixed $value )

・ __set( ) は、 アクセス不能プロパティへデータを書き込む際に実行されます。

public mixed __get ( string $name )

・ __get( ) は、 アクセス不能プロパティからデータを読み込む際に使用します。 

public bool __isset ( string $name )

・ __isset( ) は、 isset( ) あるいは empty( ) をアクセス不能プロパティに対して実行したときに起動します。 
public void __unset ( string $name ) 

・ __unset( ) は、 unset( ) をアクセス不能プロパティに対して実行したときに起動します。

引数 $name は、 操作しようとしたプロパティの名前です。
__set( ) メソッドの引数 $value は、 $name に設定しようとした値となります。

プロパティのオーバーロードはオブジェクトのコンテキストでのみ動作します
これらのマジックメソッドは、静的コンテキストでは起動しません。
したがって、これらのメソッドを static 宣言してはいけません。
PHP 5.3.0 以降、マジックオーバーロードメソッドを static 宣言すると警告が発生します。

注意:

__set( ) の返り値は無視されます。 これは、PHP が代入演算子を処理する方法によるものです。 同様に __get( ) は、

 $a = $obj->b = 8; 
のように代入と連結した場合には決してコールされません。

例1 __get( )__set( )__isset( ) および __unset( ) メソッドを使ったプロパティのオーバーロードの例

<?php
class PropertyTest
{
    
/**  オーバーロードされるデータの場所  */
    
private $data = array();

    
/**  宣言されているプロパティにはオーバーロードは起動しません */
    
public $declared 1;

    
/**  クラスの外部からアクセスした場合にのみこれがオーバーロードされます  */
    
private $hidden 2;

    public function 
__set($name$value)
    {
        echo 
"Setting '$name' to '$value'\n";
        
$this->data[$name] = $value;
    }

    public function 
__get($name)
    {
        echo 
"Getting '$name'\n";
        if (
array_key_exists($name$this->data)) {
            return 
$this->data[$name];
        }

        
$trace debug_backtrace();
        
trigger_error(
            
'Undefined property via __get(): ' $name .
            
' in ' $trace[ 0]['file'] .
            
' on line ' $trace[0]['line'],
            
E_USER_NOTICE);
        return 
null;
    }

    
/**  PHP 5.1.0 以降 */
    
public function __isset($name)
    {
        echo 
"Is '$name' set?\n";
        return isset(
$this->data[$name]);
    }

    
/**  PHP 5.1.0 以降 */
    
public function __unset($name)
    {
        echo 
"Unsetting '$name'\n";
        unset(
$this->data[$name]);
    }

    
/**  マジックメソッドではありません。単なる例として示しています  */
    
public function getHidden()
    {
        return 
$this->hidden;
    }
}


echo 
"<pre>\n";

$obj = new PropertyTest;

$obj->1;
echo 
$obj->"\n\n";

var_dump (isset($obj->a));
unset(
$obj->a);
var_dump(isset($obj->a));
echo 
"\n";

echo 
$obj->declared "\n\n";

echo 
"Let's experiment with the private property named 'hidden':\n";
echo 
"Privates are visible inside the class, so __get() not used...\n";
echo 
$obj->getHidden() . "\n";
echo 
"Privates not visible outside of class, so __get() is used...\n";
echo 
$obj->hidden "\n";
?>

上の例の出力は以下となります。

Setting 'a' to '1'
Getting 'a'
1

Is 'a' set?
bool(true)
Unsetting 'a'
Is 'a' set?
bool(false)

1

Let's experiment with the private property named 'hidden':
Privates are visible inside the class, so __get() not used...
2
Privates not visible outside of class, so __get() is used...
Getting 'hidden'


Notice:  Undefined property via __get(): hidden in <file> on line 70 in <file> on line 29

メソッドのオーバーロード

public mixed __call ( string $name , array $arguments )

・ __call( ) は、 アクセス不能メソッドをオブジェクトのコンテキストで実行したときに起動します。

public static mixed __callStatic ( string $name ,array $arguments )

・ __callStatic( ) は、 アクセス不能メソッドを静的コンテキストで実行したときに起動します。

引数 $name は、 コールしようとしたメソッドの名前です。
引数$arguments は配列で、メソッド $name に渡そうとしたパラメータが格納されます。

例2 __call( ) および __callStatic( ) メソッドによる、メソッドのオーバーロードの例 

<?php
class MethodTest
{
    public function 
__call($name$arguments)
    {
        
// 注意: $name は大文字小文字を区別します
        
echo "Calling object method '$name' "
             
implode(', '$arguments). "\n";
    }

    
/**  PHP 5.3.0 以降 */
    
public static function __callStatic($name$arguments)
    {
        
// 注意: $name は大文字小文字を区別します
        
echo "Calling static method '$name' "
             
implode(', '$arguments). "\n";
    }
}

$obj = new MethodTest;
$obj->runTest('in object context');

MethodTest::runTest('in static context');  // PHP 5.3.0 以降
?>

上の例の出力は以下となります。

Calling object method 'runTest' in object context
Calling static method 'runTest' in static context

マジックメソッドはPHPマニュアル(PHP: マジックメソッド - Manual )によると

マジックメソッド ¶

以下の関数名
__construct( ),   __destruct( ),   __call( ),   __callStatic( ),  __get( ),   __set( ),   __isset( ), __unset( ),   __sleep( ),   __wakeup( ),  __toString( ),   __invoke( ),   __set_state( ),   __clone( ) および  __debugInfo( ) は、PHP クラスにおける特殊関数の名前です。
これらの関数に関連する特別な機能を使用する場合を除き、 クラス内にこれらの名前を有する関数を作成してはいけません。

警告

PHP は、__ で始まる関数名を特殊関数として予約しています。
文書化された特殊な機能を必要とする場合を除き、 __ で始まる関数名を使用しないことが推奨されます。

__sleep( )__wakeup( )

public array __sleep ( void ) 
void __wakeup ( void ) 

serialize( ) は、クラスに特殊な名前 __sleep( ) の関数があるかどうかを調べます。
もしあれば、シリアル化の前にその関数を実行します。
この関数で、オブジェクトをクリアすることができます。
またこの関数は、シリアル化するオブジェクトについて、 すべての変数の名前を配列で返すことが前提となっています。
このメソッドが何も返さなかった場合は、NULL がシリアル化され、E_NOTICE が発生します。

注意:

__sleep( ) で、親クラスの private プロパティの名前を返すことはできません。
そうしようとすると E_NOTICE レベルのエラーとなります。
この場合は、かわりに Serializable インターフェイスを使います。

典型的な __sleep( ) の使用法は、 途中のデータをコミットしたり、 似たようなタスクのクリアを行うといったものです。
また、オブジェクトが非常に大きく、かつ、完全に保存する必要がない場合、 この関数が有用です。

逆に、unserialize( ) は、 特殊な名前 __wakeup( ) を有する 関数の存在を調べます。
もし存在する場合、この関数は、オブジェクトが有する可能性が あるあらゆるリソースを再構築することができます。

意図される __wakeup( ) の使用法は、 シリアル化の際に失われたデータベース接続を再度確立したり、 その他の再初期化を行うことです。

例1 sleep および wakeup 

<?php
class Connection
{
    protected 
$link;
    private 
$dsn$username$password;
    
    public function 
__construct($dsn$username$password)
    {
        
$this->dsn $dsn;
        
$this->username $username;
        
$this->password $password;
        
$this->connect();
    }
    
    private function 
connect()
    {
        
$this->link = new PDO($this->dsn$this->username$this->password);
    }
    
    public function 
__sleep()
    {
        return array(
'dsn''username''password');
    }
    
    public function 
__wakeup()
    {
        
$this->connect();
    }
}
?>

__toString( )

public string __toString ( void ) 

__toString( ) メソッドにより、 クラスが文字列に変換される際の動作を決めることができます。
たとえば echo $obj; としたときに何を表示させるかといったことです。
このメソッドは文字列を返さなければなりません。それ以外の場合は E_RECOVERABLE_ERROR レベルの致命的なエラーが発生します。

警告

__toString( ) メソッド内から例外を投げることはできません。
そうした場合、致命的なエラーが発生します。

例2 簡単な例

<?php
// 簡単なクラスを宣言
class TestClass
{
    public 
$foo;

    public function 
__construct($foo)
    {
        
$this->foo $foo;
    }

    public function 
__toString()
    {
        return 
$this->foo;
    }
}

$class = new TestClass('Hello');
echo 
$class;
?>

上の例の出力は以下となります。

Hello

注意が必要なのは、PHP 5.2.0 より前では、 __toString( ) メソッドは echo または print と直接結合された場合のみコールされていたということです。
PHP 5.2.0 以降では、これはすべての文字列コンテキスト (たとえば printf( ) における %s 修飾子) でコールされます。
しかし、その他の型のコンテキスト (たとえば %d 修飾子) ではコールされません。
PHP 5.2.0 以降では、__toString( ) メソッドを持っていないオブジェクトを文字列に変換しようとするとE_RECOVERABLE_ERROR が発生します。

__invoke( )

mixed __invoke ([ $... ] ) 

__invoke( ) メソッドは、 スクリプトがオブジェクトを関数としてコールしようとした際にコールされます。

注意:

この機能は PHP 5.3.0 以降で使用可能です。

例3 __invoke( ) の使用

<?php
class CallableClass
{
    public function 
__invoke($x)
    {
        
var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>

上の例の出力は以下となります。

int(5)
bool(true)

__set_state( )

static object __set_state ( array $properties ) 

この static メソッドは、 PHP 5.1.0 以降で var_export( ) によって エクスポートされたクラスのためにコールされます。

このメソッドの唯一のパラメータは、エクスポートされたプロパティを array('property' => value, ...) の形式で保持する配列です。

例4 __set_state( ) の使用法 (PHP 5.1.0 以降) 

<?php

class A
{
    public 
$var1;
    public 
$var2;

    public static function 
__set_state($an_array// PHP 5.1.0 以降
    
{
        
$obj = new A;
        
$obj->var1 $an_array['var1'];
        
$obj->var2 $an_array['var2'];
        return 
$obj;
    }
}

$a = new A;
$a->var1 5;
$a->var2 'foo';

eval(
'$b = ' var_export($atrue) . ';'); // $b = A::__set_state(array(
                                            //    'var1' => 5,
                                            //    'var2' => 'foo',
                                            // ));
var_dump($b);

?>

上の例の出力は以下となります。

object(A)#2 (2) {
  ["var1"]=>
  int(5)
  ["var2"]=>
  string(3) "foo"
} 

__debugInfo( )

array __debugInfo ( void ) 

このメソッドは、var_dump( ) がオブジェクトをダンプするときに、 プロパティの情報を取得するために呼ばれます。
もしオブジェクトにこのメソッドが定義されていなければ、 すべての public, protected, private プロパティを表示します。

この機能は PHP 5.6.0 で追加されました。

例5 __debugInfo( ) の使用法

<?php
class {
    private 
$prop;

    public function 
__construct($val) {
        
$this->prop $val;
    }

    public function 
__debugInfo() {
        return [
            
'propSquared' => $this->prop ** 2,
        ];
    }
}

var_dump(new C(42));
?>

上の例の出力は以下となります。

object(C)#1 (1) {
  ["propSquared"]=>
  int(1764)
}

Wikipediaによるとオーバーロードとは、英語で『過負荷』 という意味らしいです。

『パーフェクトPHP』によると、PHPでのオーバーロードは、

「特定の処理がクラスまたはオブジェクトに施された時のデフォルトの挙動を上書きする機能です。」 とのことです。

例:オーバーロードのプログラム

オーバーロードのプログラムを実行していきます。

$obj = new SomeClass( );
$obj->foo = 10;

 fooは宣言されていないプロパティなので、アクセス不能プロパティへの代入として__set( )メソッドが呼び出されます。

 このときの引数には、代入しようとしたプロパティ名 " foo " と代入しようとした値10が渡されます。
 この値はprivateなプロパティ$valueという配列に保存されます。

set: foo setted to 10

 続いて、" foo " を取得しようとします。

var_dump($obj->foo);

 $fooプロパティは宣言されてないので、アクセス不能なプロパティの取得を行ったことになり、__get( )メソッドが呼ばれます。

get: foo
int(10)

 続いて、isset( )、empty( )を$fooプロパティに対して実行します。

var_dump(isset($obj->foo));
var_dump(empty($obj->foo));

// " foo " という文字列を引数に__isset( )メソッドが呼ばれます。issetメソッドでは、" foo " が$valueにセットされているかを理論値で返します。

isset: foo
bool(true)
isset: foo
get: foo
bool(false)

 続いて、unset( )を呼び出します。

unset($obj->foo);
var_dump(isset($obj->foo));

 unset( )を呼び出すことにより、__unset( )が呼び出され、$valueプロパティから " foo " のキーがunsetされ、続くisset( )では結果がfalseとなります。

unset: foo
isset: foo
bool(false)

 メソッドのオーバーロードの例。

アクセス不能なメソッドへのアクセスで、__call( )、__callStatic( )が呼び出されます。

$obj->bar('baz');
SomeClass::staticBar('baz');

 上の出力の結果は以下となります。

" bar " という文字列を引数に、privateな_barメソッドや_staticBarメソッドが呼び出されています。

call: bar
bar called with arg 'baz'
callStatic: staticBar
staticBar called with arg 'baz'

 

メソッドのオーバーロードは、呼び出されるメソッド名によって処理を切り替えるなど、複雑な実装も可能だそうです。

詳しくは『パーフェクトPHP (PERFECT SERIES 3) 大型本 – 2010/11/12 小川 雄大 (著), 柄沢 聡太郎 (著), 橋口 誠 (著)』という書籍で。

f:id:ts0818:20180114145227j:plain

今回はこのへんで。