Webman AOP

v0.0.1 版本
2025-11-28 版本更新时间
2 安装
4 star

基于 Webman 框架实现的高性能、高可用的 AOP 插件,无依赖任何三方SDK实现。

特性

  • 标准 AOP 切面类型:支持 Before、After、Around、AfterReturning、AfterThrowing
  • 运行时代理:高性能的动态代理实现,无需生成文件
  • 高可用:完善的错误处理和日志记录
  • 符合 Webman 规范:使用 Bootstrap 接口和中间件机制
  • PHP 8 Attributes:使用原生属性标记切面
  • 优先级支持:多个切面按优先级执行
  • 类级别和方法级别:支持两种级别的切面
  • 抽象基类:提供 AbstractAspect 基类,简化切面开发

安装

通过 Composer 安装:

composer require x2nx/webman-aop

配置

配置文件:config/plugin/x2nx/webman-aop/app.php

<?php

return [
    // 是否启用 AOP
    'enable' => true,

    // 扫描目录
    'scan_dirs' => [
        app_path(),
    ],

    // 排除目录
    'exclude_dirs' => [
        'vendor',
        'runtime',
        'config',
        'public',
    ],
];

使用方式

方法级别切面

<?php

namespace app\controller\v1\customer;

use X2nx\WebmanAop\Attribute\Aspect;
use X2nx\WebmanAop\Aspect\LogAspect;

class IndexController
{
    #[Aspect(LogAspect::class)]
    public function index()
    {
        // 方法逻辑
    }
}

类级别切面

<?php

namespace app\controller\v1\customer;

use X2nx\WebmanAop\Attribute\Aspect;
use X2nx\WebmanAop\Aspect\LogAspect;

#[Aspect(LogAspect::class)]
class IndexController
{
    // 所有公共方法都会应用 LogAspect
    public function index() {}
    public function detail() {}
}

多个切面(按优先级)

<?php

use X2nx\WebmanAop\Attribute\Aspect;
use X2nx\WebmanAop\Aspect\LogAspect;
use app\aspect\CacheAspect;

#[Aspect(LogAspect::class, [], 100)]      // 优先级 100,后执行
#[Aspect(CacheAspect::class, [], 50)]    // 优先级 50,先执行
class IndexController
{
    // 切面执行顺序:CacheAspect -> LogAspect -> 原方法
}

创建自定义切面

方式一:实现 AspectInterface 接口

<?php

namespace app\aspect;

use X2nx\WebmanAop\Contract\AspectInterface;
use X2nx\WebmanAop\JoinPoint;

class CacheAspect implements AspectInterface
{
    public function before(JoinPoint $joinPoint): void
    {
        // 前置通知:在目标方法执行前执行
    }

    public function after(JoinPoint $joinPoint): void
    {
        // 后置通知:在目标方法执行后执行(无论是否抛出异常)
    }

    public function around(JoinPoint $joinPoint, callable $proceed): mixed
    {
        // 环绕通知:可以控制目标方法的执行
        // 可以修改参数、返回值,或决定是否执行目标方法
        return $proceed();
    }

    public function afterReturning(JoinPoint $joinPoint, mixed $result): void
    {
        // 返回后通知:在目标方法正常返回后执行
    }

    public function afterThrowing(JoinPoint $joinPoint, \Throwable $exception): void
    {
        // 异常后通知:在目标方法抛出异常后执行
    }
}

方式二:继承 AbstractAspect 基类(推荐)

<?php

namespace app\aspect;

use X2nx\WebmanAop\Contract\AbstractAspect;
use X2nx\WebmanAop\JoinPoint;
use support\Cache;

class CacheAspect extends AbstractAspect
{
    public function before(JoinPoint $joinPoint): void
    {
        // 前置:检查缓存
        $cacheKey = $this->getCacheKey($joinPoint);
        if ($cached = Cache::get($cacheKey)) {
            $joinPoint->setReturnValue($cached);
            $joinPoint->setData('from_cache', true);
        }
    }

    public function around(JoinPoint $joinPoint, callable $proceed): mixed
    {
        // 如果从缓存获取,直接返回
        if ($joinPoint->getData('from_cache')) {
            return $joinPoint->getReturnValue();
        }

        // 执行目标方法
        $result = $proceed();

        // 保存缓存
        $cacheKey = $this->getCacheKey($joinPoint);
        Cache::set($cacheKey, $result, 3600);

        return $result;
    }

    protected function getCacheKey(JoinPoint $joinPoint): string
    {
        $method = $joinPoint->getFullMethodName();
        $args = $joinPoint->getArgs();
        return 'aop_cache:' . md5($method . serialize($args));
    }
}

AOP 通知类型说明

Before(前置通知)

在目标方法执行之前执行,可以:

  • 记录日志
  • 参数验证
  • 权限检查
  • 缓存检查
public function before(JoinPoint $joinPoint): void
{
    // 在目标方法执行前执行
    Log::info('Before method execution');
}

After(后置通知)

在目标方法执行之后执行(无论是否抛出异常),可以:

  • 清理资源
  • 记录执行时间
  • 释放锁
public function after(JoinPoint $joinPoint): void
{
    // 无论是否抛出异常,都会执行
    Log::info('After method execution');
}

Around(环绕通知)

可以完全控制目标方法的执行,可以:

  • 修改参数
  • 修改返回值
  • 决定是否执行目标方法
  • 实现缓存、事务等
public function around(JoinPoint $joinPoint, callable $proceed): mixed
{
    // 前置逻辑
    $this->beforeLogic();

    // 执行目标方法(可以修改参数)
    $result = $proceed();

    // 后置逻辑(可以修改返回值)
    return $this->afterLogic($result);
}

AfterReturning(返回后通知)

在目标方法正常返回后执行(不抛出异常时),可以:

  • 记录返回值
  • 处理返回结果
  • 发送通知
public function afterReturning(JoinPoint $joinPoint, mixed $result): void
{
    // 只在正常返回时执行
    Log::info('Method returned', ['result' => $result]);
}

AfterThrowing(异常后通知)

在目标方法抛出异常后执行,可以:

  • 记录异常
  • 错误处理
  • 发送告警
public function afterThrowing(JoinPoint $joinPoint, \Throwable $exception): void
{
    // 只在抛出异常时执行
    Log::error('Method threw exception', [
        'exception' => get_class($exception),
        'message' => $exception->getMessage()
    ]);
}

通知执行顺序

当多个切面应用到同一个方法时,执行顺序如下:

  1. 所有切面的 Before 通知(按优先级顺序)
  2. 所有切面的 Around 通知(按优先级顺序,形成链式调用)
  3. 目标方法执行
  4. 所有切面的 AfterReturning 通知(如果正常返回)
  5. 所有切面的 AfterThrowing 通知(如果抛出异常)
  6. 所有切面的 After 通知(无论是否抛出异常)

JoinPoint API

// 获取目标对象
$target = $joinPoint->getTarget();

// 获取类名和方法名
$className = $joinPoint->getClassName();
$methodName = $joinPoint->getMethodName();
$fullMethodName = $joinPoint->getFullMethodName();

// 获取和设置参数
$args = $joinPoint->getArgs();
$joinPoint->setArgs($newArgs);
$arg = $joinPoint->getArg(0);
$joinPoint->setArg(0, $value);

// 执行原方法
$result = $joinPoint->proceed();

// 获取返回值和异常
$returnValue = $joinPoint->getReturnValue();
$exception = $joinPoint->getException();

// 数据存储(用于切面之间传递数据)
$joinPoint->setData('key', $value);
$value = $joinPoint->getData('key', $default);
$allData = $joinPoint->getAllData();

完整示例

缓存切面

<?php

namespace app\aspect;

use X2nx\WebmanAop\Contract\AbstractAspect;
use X2nx\WebmanAop\JoinPoint;
use support\Cache;

class CacheAspect extends AbstractAspect
{
    public function before(JoinPoint $joinPoint): void
    {
        $cacheKey = $this->getCacheKey($joinPoint);
        if ($cached = Cache::get($cacheKey)) {
            $joinPoint->setReturnValue($cached);
            $joinPoint->setData('from_cache', true);
        }
    }

    public function around(JoinPoint $joinPoint, callable $proceed): mixed
    {
        if ($joinPoint->getData('from_cache')) {
            return $joinPoint->getReturnValue();
        }

        $result = $proceed();

        $cacheKey = $this->getCacheKey($joinPoint);
        Cache::set($cacheKey, $result, 3600);

        return $result;
    }

    protected function getCacheKey(JoinPoint $joinPoint): string
    {
        return 'aop_cache:' . md5($joinPoint->getFullMethodName() . serialize($joinPoint->getArgs()));
    }
}

事务切面

<?php

namespace app\aspect;

use X2nx\WebmanAop\Contract\AbstractAspect;
use X2nx\WebmanAop\JoinPoint;
use support\Db;

class TransactionAspect extends AbstractAspect
{
    public function around(JoinPoint $joinPoint, callable $proceed): mixed
    {
        Db::beginTransaction();

        try {
            $result = $proceed();
            Db::commit();
            return $result;
        } catch (\Throwable $e) {
            Db::rollBack();
            throw $e;
        }
    }
}

许可证

MIT

赞助商