如何在调用对象任意已有方法时执行指定方法/脚本(禁用__call)
解决方案:使用装饰器模式包裹原对象
既然目标方法都已存在,__call 无法触发,那我们可以用装饰器模式来实现需求——创建一个临时的包装类,把原对象"套"进去,通过这个包装类来拦截所有方法调用,自动执行你的测试逻辑,完全不用修改原类的代码。
具体实现步骤
1. 编写装饰器类
这个类需要:
- 接收原对象作为构造参数
- 通过
__call拦截所有方法调用(因为装饰器本身没有实现原对象的方法,所以调用任何原对象的方法都会触发__call) - 在调用原对象方法前/后执行你的测试脚本,比如
testLogger()
示例代码:
class TestDecorator { private $originalObject; public function __construct($originalObject) { $this->originalObject = $originalObject; } // 拦截所有方法调用 public function __call($method, $arguments) { // 先执行你的测试逻辑 $this->testLogger(); // 调用原对象的目标方法,并返回结果 if (method_exists($this->originalObject, $method)) { return call_user_func_array([$this->originalObject, $method], $arguments); } // 方法不存在的兜底处理 throw new BadMethodCallException("Method $method does not exist"); } // 你的测试方法/脚本 private function testLogger() { echo "=== 测试日志:方法被调用了 ===" . PHP_EOL; // 这里可以替换成你需要执行的任意逻辑,比如记录日志、打印调试信息等 } }
2. 临时替换原对象为装饰器对象
在测试代码里,不用修改原对象的创建逻辑,只需要把原对象用装饰器包裹:
// 原对象创建(假设你的原类是MyClass) $original = new MyClass(); // 临时替换为装饰器对象 $testObject = new TestDecorator($original); // 现在调用任意方法都会自动执行testLogger() $testObject->m1(); $testObject->m2(); $testObject->m3();
3. 测试完成后移除装饰器
测试结束后,只需要把$testObject换回原来的$original即可,完全不用修改原类的任何代码,非常干净。
备选方案:使用runkit扩展(需要环境支持)
如果你的PHP环境安装了runkit扩展,可以动态修改类的方法,在原方法前后插入测试逻辑。不过这个方案需要服务器支持扩展,适合本地测试环境:
示例代码:
// 先保存原方法的引用 $originalM1 = [$original, 'm1']; // 重写m1方法,先执行测试逻辑再调用原方法 runkit_method_redefine( 'MyClass', 'm1', '', '$this->testLogger(); return call_user_func_array($originalM1, func_get_args());', RUNKIT_ACC_PUBLIC );
不过这个方案需要修改原类的方法定义,测试完成后需要恢复,不如装饰器方案安全和干净,所以优先推荐装饰器模式。
注意:如果原对象的方法是
final或者private,装饰器的__call无法拦截,这种情况下可以考虑用AOP扩展,但同样需要环境支持。对于临时测试来说,装饰器模式是最轻量化、无侵入的方案。
内容的提问来源于stack exchange,提问作者user1032531
相关产品推荐
相关产品推荐

