2011年11月2日星期三

PHP Mixin的改进

前段时间写了一篇关于PHP Mixin的文章,用着还挺顺手的。今日对我的Mixin类做了一点改进,让它能够用shell风格的通配符来过滤注入的方法。新的Mixin类代码如下。

class Mixable {
    private $_mixin_methods;
    private $_mixin_classes;
    public function mixin() {
        $behaviors = func_get_args();
        if ((count($behaviors) == 0) ||
            !isset($this->_mixin_methods) ||
            !isset($this->_mixin_classes)) {
            $this->_mixin_methods = array();
            $this->_mixin_classes = array();
        }
        foreach($behaviors as $b) {
            list($c, $m) = explode('-', $b.'-');
            if (empty($m)) {
                list($c, $m) = explode('+', $b.'+');
                $skip = false;
            } else {
                $skip = true;
            }
            $mfilter = empty($m) ? array() : explode(',', $m);
            if(isset($this->_mixin_classes[$c])) {
                foreach($this->_mixin_classes[$c] as $m) {
                    if ($this->_mixin_methods[$m] == $c) unset($this->_mixin_methods[$m]);
                }
                unset($this->_mixin_classes[$c]);
            }
            $methods = get_class_methods($c);
            if (is_array($methods)) {
                if (empty($mfilter)) {
                    foreach($methods as $m) {
                        $m = strtolower($m);
                        $this->_mixin_methods[$m] = $c;
                        $this->_mixin_classes[$c][] = $m;
                    }
                } else {
                    foreach($methods as $m) {
                        $m = strtolower($m);
                        $matched = false;
                        foreach($mfilter as $wildcard) {
                            if (fnmatch($wildcard, $m, FNM_CASEFOLD)) {
                                $matched = true;
                                break;
                            }
                        }
                        if ($matched == $skip) continue;
                        $this->_mixin_methods[$m] = $c;
                        $this->_mixin_classes[$c][] = $m;
                    }
                }
            }
        }
    }
    public function __call($method, $args) {
        $m = strtolower($method);
        if (isset($this->_mixin_methods[$m])) {
            $class = $this->_mixin_methods[$m];
            array_unshift($args, $this);
            return call_user_func_array(array($class, $m), $args);
        }
        trigger_error('Call to undefined function '.$method, E_USER_ERROR);
    }
}

用两段代码来说明用法:

例1:行为的继承

class Course {
    public static function greeting($obj) {
        if (get_class($obj) == 'Teacher')
            echo "Good morning class!\n";
        else
            echo "Good morning teacher!\n";
    }
}
class CourseWork extends Course {
    public static function homeworkReview($obj) {
        echo "I am reviewing homework...\n";
    }
}
class HomeWork extends Course {
    public static function homeworkStudy($obj) {
        echo "I am doing my homework...\n";
    }
}
class Teacher extends Mixable {
}
class Student extends Mixable {
}
$t = new Teacher();
$t->mixin('CourseWork');
$s = new Student();
$s->mixin('HomeWork');
$t->greeting();
$t->homeworkReview();
$s->greeting();
$s->homeworkStudy();

在这个例子中,演示了行为是可以继承的。另外要注意,行为类中的方法一般需要写作static的,虽然不写成static也可以跑,但如果php设置为STRICT模式,会有告警。

例2:选择性注入 

class Behavior {
    public static function adminAction($obj) {
        echo "> This behavior require <admin> privilege\n";
    }
    public static function userAction($obj) {
        echo "> This behavior is normal\n";
    }
}
class Power {
    public static function hyperPower($obj) {
        echo "> PHP Hyper Power...\n";
    }
}
class Main extends Mixable {
}
$m = new Main();
echo "Mixin Behavior without exclusion\n";
$m->mixin('Behavior', 'Power');
echo "Trying user action\n";
$m->userAction();
echo "Trying admin action\n";
$m->adminAction();
$opt = mt_rand() % 3;
switch($opt) {
    case 0:
        echo "Mixin Behavior while excluding admin action\n";
        $m->mixin('Behavior-Admin*');
        break;
    case 1:
        echo "Mixin Behavior while including only user action\n";
        $m->mixin('Behavior+User*');
        break;
    default:
        echo "Mixin Behavior again (clearing Hyper Power)\n";
        $m->mixin();
        $m->mixin('Behavior');
}
echo "Trying hyper power...\n";
$m->hyperPower();
echo "Trying user action\n";
$m->userAction();
echo "Trying admin action\n";
$m->adminAction();

这个例子说明以下几个用法:
  • 通过类名+方法1,方法2...(只注入这些方法)或者类名-方法1,方法2...(注入除这些方法以外的其它方法)这种表达法来选择性地注入行为类中的一部分方法
  • 如果多次注入不同的行为,先前注入的不会被取消,除非以无参数的mixin()调用来清除以前注入的类
  • 在过虑方法名的时候可以用shell通配符(大小写不区分)
  • 最后(代码中没有体现),mixin()方法的参数可变。一次mixin调用可以注入多个类,例如: $obj->mixin('Class1', 'Class2');