PHP __call魔术方法中如何回退到原生默认报错行为
PHP指定私有方法外部可调用实现方案
需求说明
- 编写PHP类时,需要实现仅白名单内的私有方法允许从类外部调用,基于
__call魔术方法实现逻辑 - 当调用的方法不在白名单内时,需要触发PHP原生致命错误,而非
trigger_error生成的E_USER_ERROR级用户自定义错误 - 适配WordPress运行场景:需要将
[$this, 'handler_method']格式的回调传入add_filter等钩子函数,但不希望将钩子处理方法设置为public,仅当方法本身需要独立对外暴露时才使用public修饰符,不会因为作为钩子回调修改方法权限。
当前原有实现
public function __call( $name, $arguments ) { if ( in_array( $name, $this->allowed_private_methods ) ) { return call_user_func_array( array( $this, $name ), $arguments ); } elseif ( is_callable( array( 'parent', '__call' ) ) ) { return parent::__call( $name, $arguments ); } elseif ( method_exists( $this, $name ) ) { trigger_error('Call to private method ' . get_class( $this ) . '::' . $name, E_USER_ERROR ); } else { trigger_error('Call to undefined method ' . get_class( $this ) . '::' . $name, E_USER_ERROR ); } }
可行实现方案
该需求可以完全实现,不需要依赖用户级错误触发:
- 只要定义了
__call魔术方法,所有外部对不可访问/不存在方法的调用都会被__call拦截,PHP不会自动触发原生错误 - 不需要使用
trigger_error,直接抛出PHP内置的Error类实例,生成的错误和PHP原生触发的致命错误格式、层级完全一致,不属于用户级错误范畴 - 针对WordPress场景,只需要将需要作为钩子回调的私有方法提前加入
$this->allowed_private_methods白名单即可,不需要修改方法的访问修饰符
优化后的代码如下:
public function __call( $name, $arguments ) { // 命中白名单,正常调用对应私有方法 if ( in_array( $name, $this->allowed_private_methods, true ) ) { return call_user_func_array( [$this, $name], $arguments ); } // 存在父类__call实现时,优先转交父类处理 if ( is_callable( ['parent', '__call'] ) ) { return parent::__call( $name, $arguments ); } // 方法存在但访问权限不足,抛出与原生一致的错误 if ( method_exists( $this, $name ) ) { throw new \Error( sprintf('Call to private method %s::%s() from invalid context', get_class($this), $name) ); } // 方法不存在,抛出与原生一致的错误 throw new \Error( sprintf('Call to undefined method %s::%s()', get_class($this), $name) ); }
注意事项
in_array判断时必须传入第三个参数true开启严格类型校验,避免松散类型判断导致的方法名误匹配- 抛出的
Error实例会进入PHP标准错误处理流程,和原生调用错误的表现完全一致,不会标记为用户级错误 - 可以在类构造函数中统一将需要注册为WordPress钩子的私有方法加入白名单,不需要单独修改每个方法的访问权限
内容的提问来源于stack exchange,提问作者Konamiman
相关产品推荐
相关产品推荐

