PHP语言动态特性-面向切面
  M9aMEIE19lAW 2023年11月02日 96 0


http://wenku.baidu.com/link?url=RxgcPmF2LUotPD2xKFLoB3koGYjhV_33iItCjFD6qvs498M8FhKDiGANNY_OjtED7yu7HZl2OpsZ4Knjm5hVloswYpuf02yTdkK9XW1Xjsi

thinkphp【面向切面编程之行为Behavior分析】

PHP语言动态特性-面向切面



PHP语言动态特性的更多用例

下面的例子类似面向切面编程,其和面向对象不同的地方在于,面向对象关注于每个对象定义的完整性,

而切面是关注于整个对象系统的某个/某些特定功能面,比如日志、过滤等,把这些特定功能面剥离出来达到共用的目的。


Listing 8. 简单的User类

<?php
class User
{
  private $name;

  function set_name( $value )
  {
    $this->name = $value;
  }

  function get_name()
  {
    return $this->name;
  }
}

$c1 = new User();
$c1->set_name( "Jack" );
$name = $c1->get_name();
echo( "name = $name\n" );
?>


这个对象足够简单,现在如果想在用户名字设置或读取的时候记录日志,该怎么做?

你可以创建一个动态的日志处理对象,把这个用户对象包装在其中,这样看起来用户对象的动作都会通知到其包装日志类一样。


Listing 9. 动态包装类

<?php

class Logged
{
  private $obj;

  function __call( $method, $args )
  {
    echo( "$method( ".join( ",", $args )." )\n" );
    return call_user_func_array(array(&$this->obj,$method), $args );
  }

  function __construct( $obj )
  {
    $this->obj = $obj;
  }
}

$c1 = new Logged( new User() );
$c1->set_name( "Jack" );
$name = $c1->get_name();
echo( "name = $name\n" );
?>


调用代码还是一样,但这一次,任何对User方法的访问,都会被echo出来。(你可以把echo替换其他的逻辑,如安全控制、日志记录、数据过滤等)

就好比在User对象的方法被调用时被某个钩子函数给挂载了,进行了预处理或后置处理。


aop简介

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。

 

aop-php简介

 

AOP-PHP是一个PECL扩展,您可以在PHP中使用面向方面的编程,无需编译或进行其他任何中间步骤。

AOP扩展的设计是最简单的方法,你可以认为PHP中的aop实现。

AOP旨在让横切关注点的分离(缓存,日志,安全,交易,……)

 

网址:http://aop-php.github.io/

 

aop-php安装

安装

有两种安装模式:

第一种方法:

sudo pecl install aop-beta  



第二种方法:



    
#Clone the repository on your computer    git clone https://github.com/AOP-PHP/AOP    cd AOP
    #prepare the package, you will need to have development tools for php
    phpize
    #compile the package
    

           ./configure --with-aop --with-php-config=/usr/bin/php-config  
   
    
make    #before the installation, check that it works properly    make test
    #install
    make install
    


错误处理

 

笔者在第二种方法安装中出现了错误(如果没有错误这里可以飘过):

 

Can't locate Autom4te/C4che.pm in @INC (@INC contains: /usr/local/share/autoconf...

解决办法是重新安装autoconf:


#wget http://ftp.gnu.org/gnu/autoconf/autoconf-latest.tar.gz#tar -zxf autoconf-latest.tar.gz#rpm -qf /usr/bin/autoconf #查看autoconf的版本
#rpm -e --nodeps autoconf-2.59-12 #卸载原来版本
#./configure --prefix=/usr
#make && make install


 

编译安装成功后,需要在php.ini里装载模块,一般在centos里php的模块装载在/etc/php.d里面,新建一个文件aop.ini ,内容为:


extension=aop.so


 

 安装成功后查看phpinfo,会看到一下内容:

PHP语言动态特性-面向切面_php源码

 

aop-php学前准备

 

专业术语

在实践之前我们需要先学习哈aop的一些专业术语。


Aspect(切面):横向切面关系被成组的放进一个类中。


Advice(通知):用于调用切面,定义某种情况下做什么和什么时间做这件事情。通知又分为:前通知、返回后通知、抛出后通知和周边通知。


Joinpoint(接入点):创建通知的位置。


Pointcut(点切割):定义了一种把通知匹配到某些接入点的方式。


 


具体可以参考这篇博客:


 


参考文档


 

了解了这些知识之后我们还需要下载aop-php的说明文档。 官方文档下载 

 


好了,E文好的可以看官方文档,直接飘过下面的文字。


 


 


准备文件

在实践之前我们需要准备四个文件:测试函数文件testfunction.php、测试类文件testclass.php、测试aop文件testaop.php和运行文件test.php。

这样做可以真实模拟我们的项目,大部分的项目都是这样布局的。

 


aop-php实践之通知

 

前通知aop_add_before

在代码中一些特殊点之前使用的通知,正常是调用一个方法或者函数。

我们先测试函数

testfunction.php代码:

<?phpfunction testFunc1(){    echo 'aop_add_before <br/>';
}


testaop.php代码:


<?php$testpoint1 = function () {echo "这是前切点测试函数:";
};
aop_add_before('testFunc1()', $testpoint1);


 

 test.php代码:


<?phprequire 'testaop.php';require 'testclass.php';
require 'testfunction.php';
header("Content-Type:text/html;charset=utf-8"); 
testFunc1();


不出意外,执行test.php我们将会看到:


这是前切点测试函数:aop_add_before


 

我们再玩哈类

testclass.php代码:



<?phpclass testClass1{
    public function testBeforAdd1()
    {
        echo get_class($this);
    }
}




testaop.php代码:



test.php代码:



<?phprequire 'testaop.php';require 'testclass.php';
require 'testfunction.php';
header("Content-Type:text/html;charset=utf-8"); 
testFunc1();
$testClass1 = new testClass1();
echo $testClass1->testBeforAdd1();


执行test.php


这是前切点测试函数:aop_add_before 这是前切点测试类方法:testClass1


再测试类属性

testclass.php源码




<?php
//测试前通知类
class testClass1
{
    public function testBeforAdd1()
    {
        echo get_class($this) .'<br />';
    }        
}
//测试前通知类属性
class testClass2
{
    private $name;
    public $publicProperty1 = 'test';
    public function __construct ($name)
    {
        $this->name = $name;
    }
    public function getName ()
    {
        return $this->name;
    }
    public function test ()
    {
        $this->publicProperty1 = 'test';
        return $this->publicProperty1;
    }
        
}


 

testaop.php源码




<?php
$testpoint11 = function  ()
{
    echo "这是前切点测试函数:";
};
$testpoint12 = function  ()
{
    echo "这是前切点测试类方法:";
};
aop_add_before('testFunc1()', $testpoint11);
aop_add_before('testClass1->testBeforAdd1()', $testpoint12);
//------测试类属性
class changeProperty
{
    public function shoot ( $who, $what)
    {
        if($what == 'test'){
            $what = '测试前通知类属性截取 <br/>';
        }
        echo "$who 想要 $what ";
    }
}
$testclass1 = new changeProperty();
$testpoint2 = function  ( AopJoinPoint $aop_tjp ) use( $testclass1 )
{
    if ( $aop_tjp->getKindOfAdvice() === AOP_KIND_BEFORE_READ_PROPERTY )
    {
        return; // 如果属性不能读则返回
    }
    elseif ( $aop_tjp->getKindOfAdvice() === AOP_KIND_BEFORE_WRITE_PROPERTY )
    {
        $testclass1->shoot($aop_tjp->getObject()->getName(),$aop_tjp->getAssignedValue());
    }
};
//测试类属性
aop_add_before('testClass2->publicProperty1', $testpoint2);


 

test.php源码


<?php
require 'testaop.php';
require 'testclass.php';
require 'testfunction.php';
header("Content-Type:text/html;charset=utf-8"); 
//前通知
testFunc1();
$testClass1 = new testClass1();
echo $testClass1->testBeforAdd1();
$runtest2 = new testClass2('skyboy');
$runtest2->test();



 

执行test.php


这是前切点测试函数:aop_add_before 这是前切点测试类方法:testClass1skyboy 想要 测试前通知类属性截取


 

返回后通知aop_add_after

在代码中一些特殊点之后使用的通知,一般是调用一个方法或者函数。

测试函数

 testfunction.php源码:


function testFunc2(){
    echo '这是返回后通知测试:';
}


testaop.php源码:


//测试返回后通知
$testpoint22 = function  ()
{
    echo "aop_add_after <br/>";
};
aop_add_after('testFunc2()', $testpoint22);


test.php源码:


//后通知
testFunc2();


 

执行test.php


这是返回后通知测试:aop_add_after


  

类和类属性和前通知类似,为了节省篇幅,这里偷懒了。

 

周边通知aop_add_around 

测试函数

testfunction.php源码:


function testFunc3($param1,$param2){
    return $param1. $param2;
}


 

testaop.php源码:



//测试周边通知

function testaround (AopJoinPoint $object)
{
    $args = $object->getArguments();
    if ($args[0] !== null) {
        $args[0] = '我想测试';
    }
    if ($args[1] !== null) {
        $args[1] = '周边通知:';
    }
    $object->setArguments($args);
    $object->process();
    
    $returnValue = $object->getReturnedValue();
    $returnValue .= 'aop_add_around<br/>';
    $object->setReturnedValue($returnValue);
    
}
aop_add_around('testFunc3()', 'testaround');




 

test.php源码:


//周边通知
echo testFunc3(1,2);


 

执行test.php


我想测试周边通知:aop_add_around


 

 

类和类属性和前通知类似。

 

aop-php函数说明

除了三个重要函数aop_add_before,aop_add_after,aop_add_around之外,我们还要记住这几个重要的函数。

getKindOfAdvice

获取通知的类型。有以下几个默认值。一般用在方法的属性更改。

• AOP_KIND_BEFORE before a given call, may it be function, method or property
• AOP_KIND_BEFORE_METHOD before a method call (method of an object)
• AOP_KIND_BEFORE_FUNCTION before a function call (not a method call)
• AOP_KIND_BEFORE_PROPERTY before a property (read or write)
• AOP_KIND_BEFORE_READ_PROPERTY before a property access (read only)
• AOP_KIND_BEFORE_WRITE_PROPERTY before a property write (write only)
• AOP_KIND_AROUND around a given call, may it be function, method or property access (read / write)
• AOP_KIND_AROUND_METHOD around a method call (method of an object)
• AOP_KIND_AROUND_FUNCTION around a function call (not a method call)
• AOP_KIND_AROUND_PROPERTY around a property (read or write)
• AOP_KIND_AROUND_READ_PROPERTY around a property access (read only)
• AOP_KIND_AROUND_WRITE_PROPERTY around a property write (write only)
• AOP_KIND_AFTER after a given call, may it be function, method or property access (read / write)
• AOP_KIND_AFTER_METHOD after a method call (method of an object)
• AOP_KIND_AFTER_FUNCTION after a function call (not a method call)
• AOP_KIND_AFTER_PROPERTY after a property (read or write)
• AOP_KIND_AFTER_READ_PROPERTY after a property access (read only)
• AOP_KIND_AFTER_WRITE_PROPERTY after a property write (write only)

getArguments

获取方法的参数。一般用在aop_add_before/aop_add_around。

setArguments

设置方法的参数。一般用在aop_add_before/aop_add_around。

getReturnedValue

获取方法的返回值。一般用在aop_add_after/aop_add_around。

setReturnedValue

设置方法的返回值。一般用在aop_add_after/aop_add_around。

process

让方法运行。一般用在aop_add_around。

 

具体详细说明,请参考官方文档。

 


aop-php开启和关闭

新建一个文件aopopenclose.php

源码如下:

<?php
ini_set("aop.enable", "1");
echo "aop is enabled<br />";
function foo ()
{
    echo "I'm foo<br />";
}
$adviceShowFoo = function  ()
{
    echo "After foo<br />";
};
aop_add_after('foo()', $adviceShowFoo);
foo();
ini_set('aop.enable', '0');
echo "aop is now disabled<br />";
foo();
echo "But you can still register new aspects<br />";
aop_add_after('f*()', $adviceShowFoo);
foo();
ini_set('aop.enable', '1');
echo "Aop is now enabled<br />";
foo();

运行结果:



aop is enabled            

             I'm foo            

             After foo            

             aop is now disabled            

             I'm foo            

             After foo            

             But you can still register new aspects            

             I'm foo            

             After foo            

             After foo            

             Aop is now enabled            

             I'm foo            

             After foo            

             After foo


  

aop-php总结

aop-php在真实意义上实现了php的aop,用户无需用其他的方式即可轻松实现。aop的编程思想是一把利刃,可以让耦合性差的项目轻松实现解耦。

全部测试文件和编辑后文件打包。点此下载。(基于ceotos环境php5.3编译)

 

 



PHP语言动态特性的更多用例

下面的例子类似面向切面编程,其和面向对象不同的地方在于,面向对象关注于每个对象定义的完整性,

而切面是关注于整个对象系统的某个/某些特定功能面,比如日志、过滤等,把这些特定功能面剥离出来达到共用的目的。


Listing 8. 简单的User类

<?php
class User
{
  private $name;

  function set_name( $value )
  {
    $this->name = $value;
  }

  function get_name()
  {
    return $this->name;
  }
}

$c1 = new User();
$c1->set_name( "Jack" );
$name = $c1->get_name();
echo( "name = $name\n" );
?>


这个对象足够简单,现在如果想在用户名字设置或读取的时候记录日志,该怎么做?

你可以创建一个动态的日志处理对象,把这个用户对象包装在其中,这样看起来用户对象的动作都会通知到其包装日志类一样。


Listing 9. 动态包装类

<?php

class Logged
{
  private $obj;

  function __call( $method, $args )
  {
    echo( "$method( ".join( ",", $args )." )\n" );
    return call_user_func_array(array(&$this->obj,$method), $args );
  }

  function __construct( $obj )
  {
    $this->obj = $obj;
  }
}

$c1 = new Logged( new User() );
$c1->set_name( "Jack" );
$name = $c1->get_name();
echo( "name = $name\n" );
?>


调用代码还是一样,但这一次,任何对User方法的访问,都会被echo出来。(你可以把echo替换其他的逻辑,如安全控制、日志记录、数据过滤等)

就好比在User对象的方法被调用时被某个钩子函数给挂载了,进行了预处理或后置处理。


by iefreer

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
M9aMEIE19lAW