AOP是時下比較流行的一種編程思想,它為程序的解耦帶來了進(jìn)一步的發(fā)展。在出現(xiàn)AOP的應(yīng)用之前,我們?nèi)绻枰诿總€程序執(zhí)行前或執(zhí)行后記錄LOG,在執(zhí)行每個代碼前進(jìn)行用戶訪問控制,就不得不重復(fù)的顯式調(diào)用同一個方法。如
創(chuàng)新互聯(lián)是一家專注于網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計與策劃設(shè)計,保定網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:保定等地區(qū)。保定做網(wǎng)站價格咨詢:18980820575
public void anyfunc(){ createLog(); /*some logic code*/ }
或
public void anyfunc(){ if(RBAC::access()){ /*some logic code*/ } }
這些“方面”的代碼會顯式的存在與每個需要它的方法中,與業(yè)務(wù)代碼形成了緊耦合的關(guān)系。而AOP的出現(xiàn)解決了固定需求的代碼與普通的業(yè)務(wù)代碼之間的隔離。為程序提供了更高的可擴展性與可修改性,更加符合面向?qū)ο缶幊痰脑瓌t。在現(xiàn)今流行的應(yīng)用于WEB開發(fā)的語言中,Java通過動態(tài)代理技術(shù)原理可以很好的實現(xiàn)AOP的思想。Python也可以通過@修飾符來實現(xiàn)AOP。而PHP確不能通過語言本身的特性來實現(xiàn)AOP的需求。
對于PHP先天性的弱勢,許多PHP框架也采取了一些措施,如Yii框架提供了beforeControllerAction與afterControllerAction方法,支持代碼執(zhí)行時的before與after操作。但與JAVA以注釋的實現(xiàn)方式相比,還是缺少了一定的靈活性。那么PHP能不能也通過注釋的方式來實現(xiàn)AOP呢?之前我一直認(rèn)為除非PHP的語言特性支持,否則要想通過注釋的方式來實現(xiàn)AOP是不可能的。但當(dāng)我在一次工作中接觸到一款名為OpenCart的開源電子商店的代碼后,受到了一點啟發(fā)。其實,我們完全可以通過重寫頁面的方法來“實現(xiàn)”注釋方式的AOP呀。下面,我將以自己做的一個demo樣本,與大家一起探討下PHP的”注釋AOP“實現(xiàn),希望能對大家有一個啟發(fā)。此段demo是在Yii框架下實現(xiàn)的,里面還有一些問題需要進(jìn)行優(yōu)化和完善。
首先給出此方法的一個邏輯流程:
思想很簡單,就是根據(jù)注釋,按照一定規(guī)則生成新的代碼文件,然后讓程序執(zhí)行新生成的文件代碼。由于每次操作都需要生成新的文件,這樣對程序的執(zhí)行效率會產(chǎn)生一定的影響,所以我們可以考慮按一定的策略來生成新文件,比如按照文件的修改時間來生成新文件,在執(zhí)行相應(yīng)操作時先判斷controller文件有無修改,如果無修改,則讀取上次生成的文件執(zhí)行,如在上次執(zhí)行的間隔時間中,controller文件有修改,則生成新的文件。
在樣例中,我們主要支持before和after的操作,before是在controller方法執(zhí)行前執(zhí)行,after是在controller方法執(zhí)行后執(zhí)行。舉個例子,有以下代碼:
/*@before_Logger_createlog*/ public function logic(){ echo 'Hello World'; }
那么在執(zhí)行l(wèi)ogic()方法時,會先執(zhí)行Logger類的createlog方法記錄log,log方法不會***到logic()方法的實體內(nèi),而是以注釋的方式存在。下面我們就來看看實現(xiàn)這項需求的具體代碼。代碼實現(xiàn)步驟:
一、找到CWebApplication.php文件,它的位置是\framework\web\CWebApplication.php。添加parseClassFile方法用來解析原有的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; //以下是具體解析注釋的過程 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; }
此代碼只是簡單的實現(xiàn)了注釋的解析工作,并且注釋不用用于Controller類中的最后一個方法(算是一個小BUG)。解析后的新內(nèi)容其實還可以進(jìn)行進(jìn)一步的壓縮代碼操作以節(jié)省文件空間,對代碼的執(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; }
此代碼只是簡單的實現(xiàn)了文件生成的功能,實際上應(yīng)該根據(jù)文件的具體修改時間來判斷是否應(yīng)該生成新的文件,除第一次生成外,只有原始Controller的代碼有過更改才應(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配置文件注冊此類。
五、在\project\protected\controllers的Controller類中找到需要進(jìn)行before或after的方法,在該方法前添加形如/*@before_Logger_createlog*/的注釋。在實際的用戶請求中,就會在執(zhí)行相應(yīng)的controller方法前或后(取決于注釋的關(guān)鍵字是before還是after)執(zhí)行切面類的方法了。
雖然通過代碼重構(gòu)的技術(shù),可以實現(xiàn)PHP的注釋AOP功能,但此種方法也有一些缺點,如I/O操作帶來的性能消耗。當(dāng)Controller文件占用空間較大時,會對執(zhí)行工作效率產(chǎn)生嚴(yán)重的影響。另外如果要讓注釋支持更多的功能,則需要提供更為復(fù)雜的解析方式,解析是通過正則表達(dá)式與替換、字符串查找方式來進(jìn)行的,而這些操作對系統(tǒng)的性能也有一定影響。所以在進(jìn)行解析時應(yīng)盡量避免內(nèi)嵌循環(huán)遍歷等操作。樣例中的代碼還有許多地方需要修改和優(yōu)化,希望此篇文章能為大家起到拋磚引玉的作用。
文章標(biāo)題:PHP注釋AOP的實現(xiàn)
網(wǎng)頁路徑:http://jinyejixie.com/article16/ipgpgg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計、面包屑導(dǎo)航、網(wǎng)站建設(shè)、定制網(wǎng)站、軟件開發(fā)、手機網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)