Coder Social home page Coder Social logo

easy_aop's Introduction

中文 | English

easy_aop

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

文档

什么是AOP?
安装
使用方法
两种Advice
Before-advice
After-advice
何时调用EasyAop::add_advice
Advice的执行可能触发另一个Advice
Advice递归
命名空间
引用参数
返回引用
异常
EasyAop::intercept

什么是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();
});

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

easy_aop's People

Contributors

nanhao avatar remicollet avatar

Stargazers

 avatar  avatar  avatar Wedan avatar  avatar Wenson Smith avatar ety001 avatar pb avatar Lostinyou avatar Littlesqx avatar  avatar yonh avatar Chris Law avatar 香芋熟了吗 avatar  avatar  avatar towser avatar  avatar  avatar 陳瀛洲 avatar Zee Kim avatar Chao’s avatar Attax avatar Ayden Zeng avatar  avatar  avatar dmls avatar

Watchers

 avatar luhix avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.