PHP类中在所有方法被调用前执行一段代码

老刘
  • 324

如下代码:

class ActivityController extends Controller
{
    public function actionDzp() {
        $this->checkAccess();
    }

    public function actionShake() {
        $this->checkAccess();        
    }

    public function actionDraw(){        
        $this->checkAccess();
    }
    
    private function checkAccess(){
        // some validation code ...
    }

ActivityController中有3个需要在被调用前执行权限校验的公开方法,所以我抽取了一个私有的checkAccess供调用.

PHP中有没有办法以无侵入的方式,在所有类的方法成员被调用前执行一段代码?而非像上面这种方式显式地调用了多次$this->checkAccess();

以AOP的方式? 还是PHP面向对象的语法中本身就有支持这种功能的魔术方法?

谢谢!

回复
阅读 7.4k
6 个回答
✓ 已被采纳

如果你的框架在支持Controller::__call()方法的话,就可以这么干

<?php
class Controller {
    public function __call($method, $args) {
        $this->checkAccess();
        
        return call_user_func_array([$this, $method], $args);
    }
    
    protected function actionFoo() {}
    protected function actionBar() {}
    public function actionBaz() {}
}

actionFoo和actionBar被声明为protected,所以调用的时候就触发__call()
actionBaz是public,就不会触发__call()

PHP原生不支持AOP,但是通过扩展可以实现:http://aop-php.github.io/
不过即使通过扩展实现,其实现过程还是没有Java那么方便。
建议是通过一些结构调整,通过控制扭转来实现AOP,比如TP会在运行控制器action方法时检查是否存在_before_action_after_action方法并执行。也可以通过类似Java Interceptor的组织逻辑去实现检查。

A) 从代码片段的角度来看,你这样挺好,不需要修改
B) 从控制器的角度来看,checkAccess方法如果在Controller类中声明为protected会更好,因为其他controller也许也需要调用这个
C) 从全局角度考虑,应该在路由分发行为之后根据module+controller+action的组合决定是否调用该检查,且该检查应该在更高级别的对象中声明,而且应该考虑声明为静态方法,如果使用了权限机制应该考虑利用权限机制完成

怎么写代码要根据需求随时调整,所以没办法给出所谓的“最佳答案”。

如果该控制中所有方法都需要权限验证,在该控制器中写个初始化方法即可,将验证过程写在_initialize()中,构造函数也可以实现__construct(),或者使用魔术方法__call(),call_user_func_array()

同样需求,我是需要在redis执行前做一次类似中间件处理,参考了 @杨益 的做法,我是先包装一个类,然后通过这个包装类去请求实际方法,因为我是用于redis,所以下面都是静态方法,非静态方法可以用__call

<?php
class Controller {
    public static function __callStatic($method, $args) {
        echo 'test:';
        
        return call_user_func_array(['ActionMet', $method], $args);
    }
}

class ActionPapa {
    public static function actionBar($name, $age) {
        echo 'bar[papa]:'.$name.':'.$age;
    }
    
}

class ActionMet extends ActionPapa {
    public static function actionFoo($name, $age) {
        echo 'foo:'.$name.':'.$age;
    }
    
    public static function actionBaz($name, $age) {
        echo 'baz:'.$name.':'.$age;
    }
}


Controller::actionBar('levi', 18);

最后说一句,我是在laravel中使用,所以我直接包装成服务对象,对外统一通过app进行调用,类似这样:

app('service')::actionBar('levi', 18);

这样无论我服务怎么修改,都没有关系

宣传栏