LoginSignup
17
16

More than 5 years have passed since last update.

PHPで不変(Immutable)オブジェクト

Last updated at Posted at 2013-09-06

 コントローラからビューにオブジェクトを渡す場合、場合によってはビュー側で操作されたくないケースもあるかと思います。

 そのような際に役立つかもしれないのが 不変(Immutable)オブジェクト。普通はImmutableな別クラスとして定義するのだと思いますが、それだと実装が複雑になってしまうので、変更操作(プロパティへのsetter、または変更メソッド)に対して例外を投げるクラスを作成してみました。

<?php
class Immutable{
    private $obj;
    private $setters_pattern;

    public function __construct( $obj, $setters_pattern = '/set([0-9a-zA-Z])*/' ){
        $this->obj = $obj;
        $this->setters_pattern = $setters_pattern;
    }

    private function checkSetters( $name ){
        if ( is_string($this->setters_pattern) ){
            return preg_match( $this->setters_pattern, $name );
        }
        else if ( is_array($this->setters_pattern) ){
            foreach( $this->setters_pattern as $pattern ){
                if ( preg_match( $pattern, $name ) )    return TRUE;
            }
            return FALSE;
        }
        return FALSE;
    }

    public function __get( $key ){
        // 内包オブジェクトに対応するプロパティがある場合は取得
        if ( property_exists( $this->obj, $key ) ){
            return $this->obj->$key;
        }
        // えっ!?
        throw new LogicException('えっ!?');
    }

    public function __set( $key, $value ){
        // $foo->barでプロパティを設定した場合は例外をスロー
        throw new Exception( "Immutableオブジェクトのため変更できません:$key" );
    }

    public function __call($name, $arguments){
        // setters_patternにマッチしたメソッドは例外をスロー
        if ( $this->checkSetters( $name ) ){
            throw new Exception( "Immutableオブジェクトのため変更できません:$name" );
        }
        // 内包オブジェクトに対応するメソッドがある場合は実行
        else if ( method_exists( $this->obj, $name ) ){
            return call_user_func_array( array($this->obj,$name), $arguments );
        }
        // えっ!?
        throw new LogicException('えっ!?'.$name);
    }
}

// テストコード
class Hoge
{
    public $foo = 'apple';
    private $bar = 'banana';

    /* getterメソッド */
    public function getBar() { return $this->bar; }

    /* setterメソッド */
    public function setBar($new_val) { $this->bar = $new_val; }

    /* setterではないがbarを変更するメソッド */
    public function changeBar() { $this->bar = 'hoge'; }
}

// 内包されるオブジェクト
$hoge = new Hoge();
// Immutableオブジェクト
$setter_patterns = array(
            '/set([0-9a-zA-Z])*/',
            '/change([0-9a-zA-Z])*/',
        );
$immutable_hoge = new Immutable( $hoge, $setter_patterns );

// プロパティ参照(getter)
print $immutable_hoge->foo . PHP_EOL;   // apple

// メソッドで参照
print $immutable_hoge->getBar() . PHP_EOL;  // banana

// 変更メソッドを呼び出す(setter)
try{
    $immutable_hoge->setBar( 'melon' );    // 例外が発生
}
catch( Exception $e ){
    print $e->getMessage() . PHP_EOL;
}

// 変更メソッドを呼び出す
try{
    $immutable_hoge->changeBar();    // 例外が発生
}
catch( Exception $e ){
    print $e->getMessage() . PHP_EOL;
}

// プロパティアクセスで設定もダメ
try{
    $immutable_hoge->foo = 'pear';    // 例外が発生
}
catch( Exception $e ){
    print $e->getMessage() . PHP_EOL;
}

// 元オブジェクト経由では普通に変更できる
$hoge->setBar( 'orange' );
print $immutable_hoge->getBar() . PHP_EOL;  // orange

実行結果
apple
banana
Immutableオブジェクトのため変更できません:setBar
Immutableオブジェクトのため変更できません:changeBar
Immutableオブジェクトのため変更できません:foo
orange

使い方は、保護したいオブジェクトをコンストラクタに渡し、オプションで変更メソッドのパターンを文字列または配列で渡します。

// 保護したいオブジェクトを作成
$hoge = new Hoge();
// Immutableオブジェクトを作成
$immutable_hoge = new Immutable( $hoge, $setter_patterns );

パターンを省略した場合は"set"で始まるメソッドをすべて変更メソッドとみなします。

$setter_patternsにマッチするメソッドが呼び出されるか、プロパティへのsetが行われた場合、例外をスローします。

17
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
16