原生php无法实现真正无侵入aop,必须控制对象创建方式;__call仅适用于无显式public方法的类,go!aop需预编译代理类且依赖容器,接口+工厂模式最稳定但要求严格契约。

原生 PHP 没有运行时字节码织入能力,所以“不修改代码”是相对的——你不需要动业务类本身,但必须控制它的创建和使用方式。否则所有所谓 AOP 都是障眼法,要么靠扩展(aop PECL),要么靠 eval 或 __call 这类高风险兜底方案。
用 __call 实现方法拦截:只适用于简单封装类
这是最轻量、零依赖的方式,但前提是目标类没声明具体方法(即不能是 final 类,且所有要拦截的方法都得走 __call),否则会直接报 Fatal error: Call to undefined method。
- 目标类必须把公开方法设为
protected或private,对外只暴露__call;或者干脆不定义任何 public 方法,全靠魔术方法调度 -
__call内部要用method_exists($this->target, $name)+call_user_func_array转发,不能硬写$this->target->$name(...),否则无法捕获不存在的方法调用 - 无法拦截静态方法、构造函数、属性访问、
__get/__set等非方法调用场景 - 性能损耗明显:每次调用多一层反射+数组 unpack,QPS 高时可观测到 5%~10% 的 CPU 上升
用 Go! AOP 框架做静态代理:适合中大型项目
Go! AOP 在 Composer 安装后,会在 cacheDir 生成真实代理类文件(如 UserServiceProxy.php),不是运行时 eval,所以兼容 XDebug、可断点、无额外开销。
- 必须用
use Go\Aop\Intercept\MethodInvocation和@Around注解,不能只写普通方法;注解字符串里的表达式(如execution(public *->save(*)))大小写敏感,save写成Save就失效 - 代理类只对「通过容器获取的对象」生效,如果业务里直接
new UserService(),切面完全不会触发 - 切入点表达式不支持正则,只支持通配符
*和部分语法糖,比如execution(*->log*(..))可以匹配所有以log开头的方法,但execution(*->log.*(..))是非法的 - 首次启动时生成代理类会卡顿几百毫秒,需在部署后预热(执行一次
php bin/console go:aop:compile)
手动代理 + 接口契约:生产环境最稳的方案
不依赖任何框架或扩展,靠 PHP 原生接口 + 工厂模式实现“透明替换”,DB/Redis 监控这种刚需场景首选它。
- 必须先定义
IDatabase接口,所有业务代码只能依赖该接口,不能直接 newPDO或Redis - 代理类(如
DatabaseProxy)要严格实现接口全部方法,漏一个就会导致TypeError;返回类型声明(如: PDOStatement)也必须一致,否则 PHP 8.1+ 会报Return type declaration must be compatible - 监控逻辑(如耗时统计)应抽成独立
MonitorAspect类,避免代理类膨胀;但注意microtime(true)调用前后不能有 I/O,否则测量失真 - 工厂类里用配置开关决定返回
new RealDatabase()还是new DatabaseProxy(new RealDatabase(), new MonitorAspect()),这个开关必须全局统一,不能一部分走代理、一部分绕过
最容易被忽略的是:所有方案都要求你放弃“直接 new 对象”的直觉。一旦业务代码里出现 new SomeService(),哪怕只有一处,整个 AOP 链就断了——监控日志不会出,事务不会回滚,权限检查也会跳过。这不是技术限制,而是契约失效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











