php7aop切面,GitHub - lyyjeep/easy_aop: A php7 extension for AOP

easy_aop

Easy_aop是一个PHP7的AOP(面向切面编程)扩展。它使你可以用最便捷的方式在任意一个函数/方法的开头或结尾动态地添加代码。同时它也支持对目标代码的拦截。

文档

什么是AOP?

假设有下面这个类:

class MyClass

{

public function method1()

{

log(); // 写日志

// method1的主要逻辑

// ...

}

public function method2()

{

log(); // 写日志

// method2的主要逻辑

// ...

}

}

我们看到 log() 重复出现在 method1() 和 method2() 的开头。它们是必须的,但并不属于方法的主要逻辑。事实上,类似这种代码可能散布在你系统当中的各个地方。所有这些 log() 形成了一个系统切面。在AOP的支持下,我么可以用一种更好的方式来写代码:

class MyClass

{

public function method1()

{

// method1 的主要逻辑

// ...

}

public function method2()

{

// method2 的主要逻辑

// ...

}

}

EasyAop::add_advice([

'before@MyClass::method1',

'before@MyClass::method2',

], function() {

log();

});

在上面的代码中,我们把这个切面单独提取了出来,log()只需要写一次。EasyAop::add_advice()会自动把它添加到 method1 和 method2 的开头。

用这种方式,我们至少获得了两种好处:

提取出了切面,这使得这个切面更容易维护

使各个方法的主逻辑保持干净

日志只是典型切面中的一个。其他常见的切面包括访问控制,统计等。

这种被动态添加的代码称为“Advice”。

安装

git clone https://github.com/nanhao/easy_aop.git

cd easy_aop

phpize

./configure

make

make test

make install

在php.ini中添加

[easy_aop]

extension=easy_aop.so

使用方法

EasyAop::add_advice([

'before@class_name::method_name',

'after@class_name::method_name',

'before@function_name',

], function($joinpoint, $args, $ret) {

// todo

});

两种Advice

有两种Advice: before-advice 和 after-advice:

before@class_name::method_name

after@class_name::method_name

Before-advice被添加在目标代码的开头,after-advice被添加在目标代码的末尾。

Before-advice

Before-advice 是在调用方向被调用的函数传递了参数之后,但被调用的函数接收到参数之前执行的:

function sum($a, $b = 10) {

return $a + $b;

}

EasyAop::add_advice(['before@sum'], function($joinpoint, $args, $ret) {

var_dump($joinpoint);

var_dump($args);

var_dump($ret);

});

sum(1);

输出:

string(8) "before@sum"

array(2) {

["a"]=>

int(1)

}

NULL

因为 $b 的默认值是在 sum 接收参数的时候被设置的,所以在 $args 中找不到 $b。换句话说,$args代表的是调用方实际传过来的参数,而不是被调用方实际接收到的参数

$ret 为NULL,因为函数还没执行

After-advice

After-advice 是在return执行完之后才被执行的。通过 $ret 可以获得实际返回的值。但在一种特殊情况下,$ret被设置为NULL,即使实际返回的似乎不是NULL:

function sum($a, $b) {

return $a + $b;

}

EasyAop::add_advice(['after@sum'], function($joinpoint, $args, $ret) {

var_dump($ret);

});

sum(1, 2);

上面的代码输出NULL而不是3。原因是:

sum(1, 2)的返回值没有被使用,PHP内核出于优化的目的把它丢弃了

何时调用EasyAop::add_advice?

EasyAop::add_advice 可以在目标代码定义之前调用:

// ok

EasyAop::add_advice(['after@sum'], function($joinpoint, $args, $ret) {

var_dump($ret);

});

function sum($a, $b) {

return $a + $b;

}

sum(1, 2);

EasyAop::add_advice 应该在目标代码执行之前调用:

// bad. advice will not run

function sum($a, $b) {

return $a + $b;

}

sum(1, 2);

EasyAop::add_advice(['after@sum'], function($joinpoint, $args, $ret) {

var_dump($ret);

});

Advice的执行可能触发另一个Advice

考虑下面代码:

EasyAop::add_advice(['after@sum'], function($joinpoint, $args, $ret) {

echo "after@sum called";

div(10, 2);

});

EasyAop::add_advice(['after@div'], function($joinpoint, $args, $ret) {

echo "after@div called";

});

function sum($a, $b) {

echo "sum called";

}

function div($a, $b) {

echo "div called";

}

sum(1, 2);

输出:

sum called

after@sum called

div called

after@div called

Advice递归

Advice递归是不允许的:

EasyAop::add_advice(['after@sum'], function($joinpoint, $args, $ret) {

sum(3, 4);

});

function sum($a, $b) {

return $a + $b;

}

sum(1, 2);

运行上面代码会导致抛出一个错误:

Fatal error: advice recursion detected: after@sum

命名空间

如果目标代码属于某个命名空间下,需要指定相对于全局命名空间的名称:

namespace A {

function sum($a, $b) {

return $a + $b;

}

}

namespace B {

\EasyAop::add_advice(['after@A\sum'], function($joinpoint, $args, $ret) {

echo "after@A\sum called" . PHP_EOL;

});

\A\sum(1, 2);

}

输出:

after@A\sum called

引用参数

function test(&$a) {

$a++;

}

EasyAop::add_advice(['after@test'], function($joinpoint, $args, $ret) {

$args['a']++;

});

$a = 1;

test($a);

var_dump($a);

Output:

int(3)

返回引用

function &test() {

global $a;

return $a;

}

EasyAop::add_advice(['after@test'], function($joinpoint, $args, &$ret) {

$ret++;

});

$a = 1;

$b = &test();

$b++;

var_dump($a);

Output:

int(3)

异常

如果在 before-advice 中抛出异常,在目标代码第一行的 try 语句会捕获到这个异常:

function test() {

try {

return 123;

}

catch (\Exception $e) {

echo $e->getMessage() . PHP_EOL;

}

}

EasyAop::add_advice(['before@test'], function($joinpoint, $args, &$ret) {

throw new \Exception('exception thrown in before-advice');

});

test();

Output:

exception thrown in before-advice

但是,上面一样的代码无法捕获到 after-advice 中跑出的异常。

原因是,before-advice被视为是目标代码的一部分,而after-advice是属于外层的。

EasyAop::intercept

你可以通过在 before-advice 中调用 EasyAop::intercept() 来拦截目标代码,拦截之后, before-advice 会取代目标代码:

function sum($a, $b) {

return $a + $b;

}

EasyAop::add_advice(['before@sum'], function($joinpoint, $args, $ret) {

EasyAop::intercept();

return $args['a'] * $args['b'];

});

$ret = sum(5, 5);

echo $ret;

Output:

25

!!!注意!!!

EasyAop::intercept()只能在before-advice中直接调用,不能在更内层的函数/方法中调用。下面代码是错误的:

function f() {

EasyAop::intercept();

}

EasyAop::add_advice(['before@sum'], function($joinpoint, $args, $ret) {

f();

});

以上代码会导致无法预料的错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值