phpでメソッドが実行された時にフィルターを実行する

April 3, 2013,
tags: php


このエントリーをはてなブックマークに追加

前々からPHPで思っていた事なんですが、クラス単位でメソッドが実行された場合にフィルターをかけたいなーとか思っていたんですね。
例えばこのメソッドを先に実行してないと他のメソッドは絶対に実行したくないとか。結構そういうケースってあると思うんですよね。

今回の条件の場合は、Hogeクラスのinitが実行されてないとHogeクラスの他のメソッドを実行しないって処理にしたい場合を想定しています。
つまり、Hoge::$initがtrueの時のみmethod1〜method3やprivateMethodやprotectedMethodが実行される感じです。

こんな感じのコードです。

<?php

/**
 * 実行したいメソッドに対してフィルター機能を提供する
 * @version 0.0.1
 * @author polidog <polidogs@gmail.com>
 *
 * @property mixed $object 実行するクラスのインスタンス
 * @property boolean $isExecuteProtectMethod private, protectedなメソッドを実行するかのフラグ
 */
abstract class MethodFilter {

  protected $object;
  protected $isExecuteProtectMethod = false;

  /**
   * コンストラクタ
   * @param object $object
   * @throws MethodFilterExecption
   */
  public function __construct($object) {
    if (is_object($object)) {
      $this->object = $object;
    } else {
      throw new MethodFilterExecption('no set object');
    }
  }

  /**
   * メソッドが実行された場合の動作
   * @param string $name
   * @param array $arguments
   * @return mixed
   * @throws MethodFilterExecption
   */
  public function __call($name, $arguments) {
    $this->callPreFilter($name, $arguments);
    if (!$this->isExecuteMethod($name)) {

      if (!$this->isExecuteProtectMethod) {
        throw new MethodFilterExecption('method not exist! name:' . $name);
      }

      $method = new ReflectionMethod($this->object, $name);
      $method->setAccessible(true);
      $return =  $method->invokeArgs($this->object, $arguments);
    } else {
      $return = call_user_func_array(array($this->object, $name), $arguments);
    }
    $return = $this->callPostFilter($name, $arguments, $return);
    return $return;
  }

  /**
   * インスタンス変数でそのままコールされた場合の処理
   * @param string $name
   * @param array $arguments
   * @return mixed
   */
  public function __invoke($name, $arguments = array()) {
    return $this->__call($name, $arguments);
  }

  /**
   * クラス変数をセットする
   * @param string $name 変数名
   * @param mixed $value 変数にセットする名前
   */
  public function __set($name, $value) {
    if ($name == 'isExecuteProtectMethod') {
      if (is_bool($value)) {
        $this->isExecuteProtectMethod = $value;
      }
    }
  }

  /**
   * 実行できるメソッドか判定する
   * @param string $name
   * @return boolean
   */
  protected function isExecuteMethod($name) {
    return is_callable(array($this->object, $name));
  }

  /**
   * 実行前フィルタークラス
   * @param string $name 実行するメソッド名
   * @param array $arguments 実行するメソッドの引数
   */
  abstract protected function callPreFilter($name, $arguments);

  /**
   * 実行後フィルタークラス
   * @param string $name 実行したメソッド名
   * @param array $arguments 実行したメソッドの引数
   */
  abstract protected function callPostFilter($name, $arguments, $return);
}

class MethodFilterExecption extends ErrorException {

}

実行したいクラスとMethodFilterを継承したクラス(Hoge.php)

<?php
require 'MethodFilter.php';
class Hoge
{
  private $isInit = false;

  public function init() {
    // 何らかの処理...
    $this->isInit = true;
  }

  public function isInit() {
    // initメソッドが実行されているか確認する
    return $this->isInit;
  }

  public function method1() {
    echo "execute method 1";
  }

  public function method2() {
    echo "execute method 2";
  }

  private function privateMethod() {
    echo "execute private method";
  }

  protected function protectedMethod() {
    echo "execute protected method";
  }
}

class HogeFilter extends MethodFilter
{
  protected function callPreFilter($name, $arguments) {
    if ($name == 'init') return null;

    if (! $this->object->isInit()) {
      throw new Exception("no execute init method");
    }

  }

  protected function callPostFilter($name, $arguments, $return) {
    if ($name == 'init') return $return;
    echo "\n";
    return $return;
  }
}

実際の使い方はこんな感じです。
main.php

<?php
require "Hoge.php";


$object = new HogeFilter(new Hoge());
$object->init();
// initが実行されている時のみ実行される
$object->method1();

// initしてないとexecptionをthowする
$object2 = new HogeFilter(new Hoge());
try {
  $object2->method1();
} catch (Exception $e) {
  echo $e->getMessage()."\n";
}

// privateやprotectedなメソッドだって実行できるよ!
$object3 = new HogeFilter(new Hoge());
$object3->init();
$object3->isExecuteProtectMethod = true;
$object3->privateMethod();
$object3->protectedMethod();

実行結果

polidog$ php main.php
execute method 1
no execute init method
execute private method
execute protected method

クラス単位というかインスタンス単位でフィルターを使いたいときにめっちゃ便利だと思います!

comments powered by Disqus