6.2 PHP实践 

目前的PHP来说,还没有一个完整的AOP内置实现,虽然出现了RunKit, 但一直都以BETA的状态呆在PECL项目里,估计很长时间内不太可能成为PHP的缺省设置。那是不是AOP在PHP里就破灭了呢?当然不是,因为我们有 __get(),__set(),__call()等魔术方法,合理使用这些方法可以为我们实现某种程度的“准AOP”能力,之所以说是准AOP,是因为 单单从实现上来看,称其为AOP有些牵强,但是从效果上来看,又部分实现了AOP的作用,虽然其实现方式并不完美,但对于一般的使用已经足够了。

  1. <?php  

  2. /** 

  3.  * 应用程序中某个业务逻辑类 

  4.  * 

  5.  */  

  6. class Target  

  7. {  

  8.     public function foobar(){  

  9.         echo '业务逻辑<br />';  

  10.     }  

  11. }  

  12.   

  13. //业务逻辑类的包装类  

  14. class AOP  

  15. {  

  16.     private $instance;  

  17.   

  18.     public function __construct($instance) {  

  19.         $this->instance = $instance;  

  20.     }  

  21.   

  22.     public function __call($method, $param) {  

  23.         if(! method_exists($this->instance, $method)) {  

  24.             throw new Exception("Call undefinded method ".get_class($this->instance)."::$method");  

  25.         }  

  26.           

  27.         //前增强  

  28.         $this->before();  

  29.         $callBack = array($this->instance, $method);  

  30.         $return = call_user_func_array($callBack, $param);  

  31.         $this->after();  

  32.         return $return;  

  33.     }  

  34.     /** 

  35.      * 前增强 

  36.      * 

  37.      */  

  38.     public function  before() {  

  39.         echo '权限检查<br />';  

  40.     }  

  41.     /** 

  42.      * 后增强 

  43.      * 

  44.      */  

  45.     public function after() {  

  46.         echo '日志记录<br />';  

  47.     }  

  48. }  

  49.   

  50.   

  51. /** 

  52.  * 工厂方法 

  53.  * 

  54.  */  

  55. class Factory  

  56. {  

  57.     public function getTargetInstance(){  

  58.         return new AOP(new Target());  

  59.     }  

  60. }  

  61.   

  62. //客户端调用演示  

  63. header("Content-Type: text/html; charset=utf8");  

  64. try {  

  65.     $obj = Factory::getTargetInstance();  

  66.     $obj->foobar();  

  67. catch(Exception $e) {  

  68.     echo 'Caught exception: ',  $e->getMessage();  

  69. }  


利用php5内置的魔术方法__call来实现AOP,唯一的缺点是要在AOP类里面实例客户端对象,返回的是被AOP包装后的对象。因此像get_class会遇到麻烦。

比 如说,客户端通过getBizInstance()方法以为得到的对象是Target,但实际上它得到的是一个Target的包装对象AOP,这样的话, 如果客户端进行一些诸如get_class()之类和对象类型相关的操作就会出错,当然,大多数情况下,客户端似乎不太会做类似的操作。

其实我们在代理模式也提到过,这其实就是一个动态代理模式。

我们用 runkit 扩展来实现方法调用拦截的例子:

  1. /** 

  2.  * 应用程序中某个业务逻辑类 

  3.  * 

  4.  */  

  5. class Target  

  6. {  

  7.     public function foobar(){  

  8.         echo '业务逻辑<br />';  

  9.     }  

  10. }  

  11. runkit_method_rename('Target''foobar''#foobar');  

  12. runkit_method_add('Target','add','$a,$b','  

  13.     echo "before call\n";  

  14.     $ret = $this->{"#foobar"}($a,$b);  

  15.     echo "after call\n";  

  16.     return $ret;  

  17. ');  

也有人用了继承方式来实现:

  1. <?php  

  2.   

  3.   

  4. //业务逻辑类的包装类  

  5. class AOP  

  6. {  

  7.     private $instance;  

  8.   

  9.     public function __construct() {  

  10.     }  

  11.   

  12.     public function __call($method$param) {  

  13.         if(strchr($method,'Aop_')){  

  14.             $method = str_replace('Aop_','',$method);  

  15.              if(! method_exists($this$method)) {  

  16.                 throw new Exception("Call undefinded method ".get_class($this)."::$method");  

  17.             }  

  18.         }  

  19.         //前增强  

  20.         $this->before();  

  21.         $callBack = array($this$method);  

  22.         $return = call_user_func_array($callBack$param);  

  23.         $this->after();  

  24.         return $return;  

  25.     }  

  26.     /** 

  27.      * 前增强 

  28.      * 

  29.      */  

  30.     public function  before() {  

  31.         echo '权限检查<br />';  

  32.     }  

  33.     /** 

  34.      * 后增强 

  35.      * 

  36.      */  

  37.     public function after() {  

  38.         echo '日志记录<br />';  

  39.     }  

  40. }  

  41.   

  42.   

  43. /** 

  44.  * 应用程序中某个业务逻辑类 

  45.  * 

  46.  */  

  47. class Target extends AOP   

  48. {  

  49.     public function foobar(){  

  50.         echo '业务逻辑<br />';  

  51.     }  

  52. }  

  53.   

  54.   

  55. //客户端调用演示  

  56. header("Content-Type: text/html; charset=utf8");  

  57. try {  

  58.     $obj = new  Target();  

  59.     $obj->Aop_foobar();  

  60. } catch(Exception $e) {  

  61.     echo 'Caught exception: ',  $e->getMessage();  

  62. }  

这个有明显的缺点:

1)严格的限制,如需要增强的方法名需要加AOP前缀。
2)如果Target类已经继承另外的父类,无法在继承AOP类。
3) AOP的实现,一个很重要的前提就是不能对源代码有很明显的侵入,而这里增强的目标对象要继承AOP类,无疑侵入了对象.

     除了以上的实现方法,我们可以使用配置文件来配置把哪些关注点代码增强到目标对象的切入点上。