PHP方法执行前修改入参的最佳实现方案
方法实际执行前修改入参的最佳实现
核心需求为在方法实际执行前对方法入参做过滤/修改,原有实现采用Attribute+装饰器/代理模式的思路是可行的,参考实现代码如下:
#[\Attribute] class Query { } class Foo { #[Query] public function request(array $query = []) { } public function foo(array $query = []) { } #[Query] public function bar(string $name, int $age, array $query = []) { } } class FooDecorator { private Foo $target; public function __construct(Foo $target) { $this->target = $target; } public function __call(string $methodName, array $args) { $class = get_class($this->target); try { $reflection = new \ReflectionClass($class); $methods = $reflection->getMethods(); $attributeName = __NAMESPACE__ . '\Query'; foreach ($methods as $method) { if ($method->getName() !== $methodName) { continue; } $attributes = $method->getAttributes(); foreach ($attributes as $attribute) { if ($attribute->getName() === $attributeName) { $parameters = $method->getParameters(); foreach ($parameters as $key => $param) { if ($param->getName() === 'query') { // 过滤名为query的参数 $args[$key] = $this->validateQueryParameter($args[$key]); break; } } } } } } catch (\Exception $e) { } if (method_exists($this->target, $methodName)) { return call_user_func_array([$this->target, $methodName], $args); } } private function validateQueryParameter(array $query): array { $allowed = [ 'foo', 'bar', ]; $query = array_filter($query = array_change_key_case($query), function ($key) use ($allowed) { // 过滤非白名单内的键 return in_array($key, $allowed); }, ARRAY_FILTER_USE_KEY); return $query; } } $foo = new FooDecorator(new Foo()); // 会移除faz、baz键,仅保留foo $foo->query(['faz' => 1, 'baz' => 2, 'foo' => 3]); // 不做任何参数处理 $foo->foo(['baz' => 1]); // 会移除faz、baz键,仅保留foo $foo->bar('foo', 100, ['faz' => 1, 'baz' => 2, 'foo' => 3]);
该实现可以达到预期的参数过滤效果,但存在明显缺陷:
- 依赖
__call魔术方法实现代理,IDE无法给出原Foo类所有方法的代码提示 - 每次方法调用都会执行全量反射扫描,性能开销偏高
- 若通过定义接口、在装饰器中实现全部方法解决提示问题,面对业务类大量方法的场景会产生大量冗余代码,维护成本极高
针对这类场景,有三个成熟的落地方案,按推荐优先级排序:
方案1:使用AOP(面向切面编程)实现横切逻辑
参数过滤属于典型的横切关注点,用AOP实现是行业标准方案,完全可以替代手动编写装饰器:
- 不需要修改原有业务类代码,不需要手动编写包装类,无冗余代码
- 注解扫描、方法匹配逻辑会在应用启动阶段完成并缓存,运行时无重复反射开销,性能远高于手写装饰器
- 配合IDE对应插件可以保留完整的方法代码提示,不会出现识别不到方法的问题
实现时只需要定义一个切面,匹配所有携带#[Query]注解的方法,在前置通知中完成query参数的过滤替换即可。
方案2:保留现有装饰器逻辑,通过PHPDoc解决IDE提示
如果不想引入额外的AOP组件,可以在装饰器类头部添加@method PHPDoc注解,直接告知IDE当前代理类支持的所有方法,不需要逐个实现方法:
/** * 以下注解可通过IDE插件自动生成,无需手动编写 * @method mixed request(array $query = []) * @method mixed foo(array $query = []) * @method mixed bar(string $name, int $age, array $query = []) */ class FooDecorator { // 原有逻辑不变,建议把反射扫描逻辑移到构造函数中做一次缓存,避免每次调用重复反射 }
主流IDE(PhpStorm等)都支持识别这类@method注解,写完后即可恢复正常的代码提示、参数补全。如果被代理类方法很多,可以直接用IDE的注解生成功能,一键生成所有方法的@method声明,几秒钟即可完成,不需要手动编写。
方案3:基于框架内置的参数处理管道实现
如果使用Laravel、Symfony这类主流现代PHP框架,不需要自己实现代理层:
框架本身已经实现了完整的方法参数注入、注解识别流程,可以直接自定义参数转换器/中间件,识别方法上的#[Query]注解,在参数注入业务方法之前就完成query字段的过滤。这种方案完全没有额外代理层开销,IDE提示100%兼容,也符合框架的开发规范,是业务开发场景下成本最低的实现方式。
内容的提问来源于stack exchange,提问作者Cyclonecode
相关产品推荐
相关产品推荐

