AOP是時(shí)下比較流行的一種編程思想,它為程序的解耦帶來(lái)了進(jìn)一步的發(fā)展。在出現(xiàn)AOP的應(yīng)用之前,我們?nèi)绻枰诿總€(gè)程序執(zhí)行前或執(zhí)行后記錄LOG,在執(zhí)行每個(gè)代碼前進(jìn)行用戶訪問控制,就不得不重復(fù)的顯式調(diào)用同一個(gè)方法。如
創(chuàng)新互聯(lián)是一家專注于網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)與策劃設(shè)計(jì),保定網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:保定等地區(qū)。保定做網(wǎng)站價(jià)格咨詢:18980820575
public void anyfunc(){
createLog();
/*some logic code*/
}或
public void anyfunc(){
if(RBAC::access()){
/*some logic code*/
}
}這些“方面”的代碼會(huì)顯式的存在與每個(gè)需要它的方法中,與業(yè)務(wù)代碼形成了緊耦合的關(guān)系。而AOP的出現(xiàn)解決了固定需求的代碼與普通的業(yè)務(wù)代碼之間的隔離。為程序提供了更高的可擴(kuò)展性與可修改性,更加符合面向?qū)ο缶幊痰脑瓌t。在現(xiàn)今流行的應(yīng)用于WEB開發(fā)的語(yǔ)言中,Java通過(guò)動(dòng)態(tài)代理技術(shù)原理可以很好的實(shí)現(xiàn)AOP的思想。Python也可以通過(guò)@修飾符來(lái)實(shí)現(xiàn)AOP。而PHP確不能通過(guò)語(yǔ)言本身的特性來(lái)實(shí)現(xiàn)AOP的需求。
對(duì)于PHP先天性的弱勢(shì),許多PHP框架也采取了一些措施,如Yii框架提供了beforeControllerAction與afterControllerAction方法,支持代碼執(zhí)行時(shí)的before與after操作。但與JAVA以注釋的實(shí)現(xiàn)方式相比,還是缺少了一定的靈活性。那么PHP能不能也通過(guò)注釋的方式來(lái)實(shí)現(xiàn)AOP呢?之前我一直認(rèn)為除非PHP的語(yǔ)言特性支持,否則要想通過(guò)注釋的方式來(lái)實(shí)現(xiàn)AOP是不可能的。但當(dāng)我在一次工作中接觸到一款名為OpenCart的開源電子商店的代碼后,受到了一點(diǎn)啟發(fā)。其實(shí),我們完全可以通過(guò)重寫頁(yè)面的方法來(lái)“實(shí)現(xiàn)”注釋方式的AOP呀。下面,我將以自己做的一個(gè)demo樣本,與大家一起探討下PHP的”注釋AOP“實(shí)現(xiàn),希望能對(duì)大家有一個(gè)啟發(fā)。此段demo是在Yii框架下實(shí)現(xiàn)的,里面還有一些問題需要進(jìn)行優(yōu)化和完善。
首先給出此方法的一個(gè)邏輯流程:


思想很簡(jiǎn)單,就是根據(jù)注釋,按照一定規(guī)則生成新的代碼文件,然后讓程序執(zhí)行新生成的文件代碼。由于每次操作都需要生成新的文件,這樣對(duì)程序的執(zhí)行效率會(huì)產(chǎn)生一定的影響,所以我們可以考慮按一定的策略來(lái)生成新文件,比如按照文件的修改時(shí)間來(lái)生成新文件,在執(zhí)行相應(yīng)操作時(shí)先判斷controller文件有無(wú)修改,如果無(wú)修改,則讀取上次生成的文件執(zhí)行,如在上次執(zhí)行的間隔時(shí)間中,controller文件有修改,則生成新的文件。
在樣例中,我們主要支持before和after的操作,before是在controller方法執(zhí)行前執(zhí)行,after是在controller方法執(zhí)行后執(zhí)行。舉個(gè)例子,有以下代碼:
/*@before_Logger_createlog*/
public function logic(){
echo 'Hello World';
}那么在執(zhí)行l(wèi)ogic()方法時(shí),會(huì)先執(zhí)行Logger類的createlog方法記錄log,log方法不會(huì)***到logic()方法的實(shí)體內(nèi),而是以注釋的方式存在。下面我們就來(lái)看看實(shí)現(xiàn)這項(xiàng)需求的具體代碼。代碼實(shí)現(xiàn)步驟:
一、找到CWebApplication.php文件,它的位置是\framework\web\CWebApplication.php。添加parseClassFile方法用來(lái)解析原有的Controller類,代碼如下:
public function parseClassFile($id,$aopconfigpath){
$className=ucfirst($id).'Controller';
$classFile=$this->getControllerPath().DIRECTORY_SEPARATOR.$className.'.php';
if(!is_file($classFile)){
return false;
}
$originalContent = file_get_contents($classFile);
//獲取原始Controller的內(nèi)容
$mark_patten = '/\/\*@(before_\w+|after_\w+)\*\//s';
$mark_patten1 = '/(\/\*@before_[^\*]+\*\/|\/\*@after_[^\*]+\*\/)/s';
$mark_patten2 = '/(\/\*@before_[^\*]+\*\/[^\d]|\/\*@after_[^\*]+\*\/[^\d])/s';
preg_match_all($mark_patten1, $originalContent,$mark,PREG_OFFSET_CAPTURE);
if(empty($mark[0])){
return $originalContent;
//如果沒有找到相關(guān)的AOP注釋,則直接返回原始內(nèi)容
}
$newContent = $originalContent;
//以下是具體解析注釋的過(guò)程
foreach($mark[0] as $k=>$m){
if($k!=0){
preg_match_all($mark_patten2, $newContent,$submark,PREG_OFFSET_CAPTURE);
$m[1] = $submark[0][0][1];
$m[0] = $submark[0][0][0];
}
$front_part = substr($newContent,0,$m[1]);
$behind_part = substr($newContent,$m[1]+strlen($m[0]));
$replace_part = preg_replace('/\s+/', '', $m[0]).$m[1];
$exact_pattens[] = $replace_part;
$newContent = $front_part.$replace_part."\n".$behind_part;
}
foreach($exact_pattens as $k=>$exact_patten){
$exact_patten = addcslashes($exact_patten,'/*');
preg_match('/'.$exact_patten.'([^\{]+)/s',$newContent,$fun);
$start = stripos($newContent,$fun[1])+strlen($fun[1]);
$step1 = substr($newContent,$start);
preg_match_all('/(public\r*\n*\s+function\r*\n*\s+|protected\r*\n*\s+function\r*\n*\s+|private\r*\n*\s+function\r*\n*\s+)/s',$step1,$fun_mark,PREG_OFFSET_CAPTURE);
$count_fun_mark = count($fun_mark[0]);
$fun_mark_arr = array();
for($i=0;$i<$count_fun_mark;$i++){
$fun_mark_arr[] = $fun_mark[0][$i][1];
}
sort($fun_mark_arr,SORT_NUMERIC);
$nearly_fun_position = $fun_mark_arr[0];
$step2 = substr($step1,0,$nearly_fun_position);
$code_start_position = strpos($step2,'{');
$code_end_position = strripos($step2,'}');
$find_fun_patten = '/@\w+_(\w+)_(\w+)/';
preg_match($find_fun_patten, $exact_patten,$funname_arr);
$command = 'Yii::app()->'.$funname_arr[1].'->'.$funname_arr[2].'();';
//需要執(zhí)行方面的代碼
$insert_part = $command;
if(strpos($exact_patten,'before_')){
$front_part = substr($step2, 0,$code_start_position+1);
$behind_part = substr($step2,$code_start_position+1);
}
else if(strpos($exact_patten,'after_')){
$front_part = substr($step2,0,$code_end_position);
$behind_part = substr($step2,$code_end_position);
}
$replace_part = $front_part.$insert_part.$behind_part;
$replace_parts[] = $replace_part;
$need_replace_parts[] = $step2;
}
$result = str_replace($need_replace_parts, $replace_parts, $originalContent);
//生成解析好的Controller內(nèi)容
return $result;
}此代碼只是簡(jiǎn)單的實(shí)現(xiàn)了注釋的解析工作,并且注釋不用用于Controller類中的最后一個(gè)方法(算是一個(gè)小BUG)。解析后的新內(nèi)容其實(shí)還可以進(jìn)行進(jìn)一步的壓縮代碼操作以節(jié)省文件空間,對(duì)代碼的執(zhí)行效率也有一定的好處。
二、在CWebApplication.php文件內(nèi)添加createCompilerController以生成新的Controller文件。代碼如下:
public function createCompilerController($classname,$basepath,$controllercontents){
if($controllercontents==false){
return false;
}
$compiler_file = $basepath.DIRECTORY_SEPARATOR.$classname.'.php';
file_put_contents($compiler_file, $controllercontents);
return true;
}此代碼只是簡(jiǎn)單的實(shí)現(xiàn)了文件生成的功能,實(shí)際上應(yīng)該根據(jù)文件的具體修改時(shí)間來(lái)判斷是否應(yīng)該生成新的文件,除第一次生成外,只有原始Controller的代碼有過(guò)更改才應(yīng)該生成新的文件,否則就應(yīng)該用上一次生成的Controller文件。
三、在CWebApplication.php文件的createController方法內(nèi)找到以下代碼:
if(!isset($basePath)) // first segment
{
if(isset($owner->controllerMap[$id]))
{
return array(
Yii::createComponent($owner->controllerMap[$id],$id,$owner===$this?null:$owner),
$this->parseActionParams($route),
);
}
if(($module=$owner->getModule($id))!==null)
return $this->createController($route,$module);
$originalBasePath = $basePath=$owner->getControllerPath();
$controllerID='';
}
else
$controllerID.='/';
$className=ucfirst($id).'Controller';在下方添加以下的邏輯代碼:
$change_read_controller_flag = false;
//框架是執(zhí)行新的Controller文件還是執(zhí)行原始Controller文件,true代表執(zhí)行新的Controller文件。
if($controllerID==''){
$aopConfigPath = $this->getAopConfigPath();
$controllerContents = $this->parseClassFile($id,$aopConfigPath);
$basePath = $this->getCompilerControllerPath();
if($this->createCompilerController($className,$basePath,$controllerContents)){
//如果創(chuàng)建新文件成功,則框架讀取新的Controller文件。
$change_read_controller_flag = true;
}
}
if(!$change_read_controller_flag){
$basePath = $this->getControllerPath();
}
$classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
四、在\project\protected\components下建立AOP類,如Logger類及相應(yīng)的方法,并在\project\protected\config\main.php配置文件注冊(cè)此類。
五、在\project\protected\controllers的Controller類中找到需要進(jìn)行before或after的方法,在該方法前添加形如/*@before_Logger_createlog*/的注釋。在實(shí)際的用戶請(qǐng)求中,就會(huì)在執(zhí)行相應(yīng)的controller方法前或后(取決于注釋的關(guān)鍵字是before還是after)執(zhí)行切面類的方法了。
雖然通過(guò)代碼重構(gòu)的技術(shù),可以實(shí)現(xiàn)PHP的注釋AOP功能,但此種方法也有一些缺點(diǎn),如I/O操作帶來(lái)的性能消耗。當(dāng)Controller文件占用空間較大時(shí),會(huì)對(duì)執(zhí)行工作效率產(chǎn)生嚴(yán)重的影響。另外如果要讓注釋支持更多的功能,則需要提供更為復(fù)雜的解析方式,解析是通過(guò)正則表達(dá)式與替換、字符串查找方式來(lái)進(jìn)行的,而這些操作對(duì)系統(tǒng)的性能也有一定影響。所以在進(jìn)行解析時(shí)應(yīng)盡量避免內(nèi)嵌循環(huán)遍歷等操作。樣例中的代碼還有許多地方需要修改和優(yōu)化,希望此篇文章能為大家起到拋磚引玉的作用。
文章標(biāo)題:PHP注釋AOP的實(shí)現(xiàn)
網(wǎng)頁(yè)路徑:http://chinadenli.net/article16/ipgpgg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計(jì)、面包屑導(dǎo)航、網(wǎng)站建設(shè)、定制網(wǎng)站、軟件開發(fā)、手機(jī)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)