您好,登錄后才能下訂單哦!
AOP是時下比較流行的一種編程思想,它為程序的解耦帶來了進一步的發展。在出現AOP的應用之前,我們如果需要在每個程序執行前或執行后記錄LOG,在執行每個代碼前進行用戶訪問控制,就不得不重復的顯式調用同一個方法。如
public void anyfunc(){ createLog(); /*some logic code*/ }
或
public void anyfunc(){ if(RBAC::access()){ /*some logic code*/ } }
這些“方面”的代碼會顯式的存在與每個需要它的方法中,與業務代碼形成了緊耦合的關系。而AOP的出現解決了固定需求的代碼與普通的業務代碼之間的隔離。為程序提供了更高的可擴展性與可修改性,更加符合面向對象編程的原則。在現今流行的應用于WEB開發的語言中,Java通過動態代理技術原理可以很好的實現AOP的思想。Python也可以通過@修飾符來實現AOP。而PHP確不能通過語言本身的特性來實現AOP的需求。
對于PHP先天性的弱勢,許多PHP框架也采取了一些措施,如Yii框架提供了beforeControllerAction與afterControllerAction方法,支持代碼執行時的before與after操作。但與JAVA以注釋的實現方式相比,還是缺少了一定的靈活性。那么PHP能不能也通過注釋的方式來實現AOP呢?之前我一直認為除非PHP的語言特性支持,否則要想通過注釋的方式來實現AOP是不可能的。但當我在一次工作中接觸到一款名為OpenCart的開源電子商店的代碼后,受到了一點啟發。其實,我們完全可以通過重寫頁面的方法來“實現”注釋方式的AOP呀。下面,我將以自己做的一個demo樣本,與大家一起探討下PHP的”注釋AOP“實現,希望能對大家有一個啟發。此段demo是在Yii框架下實現的,里面還有一些問題需要進行優化和完善。
首先給出此方法的一個邏輯流程:
思想很簡單,就是根據注釋,按照一定規則生成新的代碼文件,然后讓程序執行新生成的文件代碼。由于每次操作都需要生成新的文件,這樣對程序的執行效率會產生一定的影響,所以我們可以考慮按一定的策略來生成新文件,比如按照文件的修改時間來生成新文件,在執行相應操作時先判斷controller文件有無修改,如果無修改,則讀取上次生成的文件執行,如在上次執行的間隔時間中,controller文件有修改,則生成新的文件。
在樣例中,我們主要支持before和after的操作,before是在controller方法執行前執行,after是在controller方法執行后執行。舉個例子,有以下代碼:
/*@before_Logger_createlog*/ public function logic(){ echo 'Hello World'; }
那么在執行logic()方法時,會先執行Logger類的createlog方法記錄log,log方法不會***到logic()方法的實體內,而是以注釋的方式存在。下面我們就來看看實現這項需求的具體代碼。代碼實現步驟:
一、找到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的內容 $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; //如果沒有找到相關的AOP注釋,則直接返回原始內容 } $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].'();'; //需要執行方面的代碼 $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內容 return $result; }
此代碼只是簡單的實現了注釋的解析工作,并且注釋不用用于Controller類中的最后一個方法(算是一個小BUG)。解析后的新內容其實還可以進行進一步的壓縮代碼操作以節省文件空間,對代碼的執行效率也有一定的好處。
二、在CWebApplication.php文件內添加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; }
此代碼只是簡單的實現了文件生成的功能,實際上應該根據文件的具體修改時間來判斷是否應該生成新的文件,除第一次生成外,只有原始Controller的代碼有過更改才應該生成新的文件,否則就應該用上一次生成的Controller文件。
三、在CWebApplication.php文件的createController方法內找到以下代碼:
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; //框架是執行新的Controller文件還是執行原始Controller文件,true代表執行新的Controller文件。 if($controllerID==''){ $aopConfigPath = $this->getAopConfigPath(); $controllerContents = $this->parseClassFile($id,$aopConfigPath); $basePath = $this->getCompilerControllerPath(); if($this->createCompilerController($className,$basePath,$controllerContents)){ //如果創建新文件成功,則框架讀取新的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類及相應的方法,并在\project\protected\config\main.php配置文件注冊此類。
五、在\project\protected\controllers的Controller類中找到需要進行before或after的方法,在該方法前添加形如/*@before_Logger_createlog*/的注釋。在實際的用戶請求中,就會在執行相應的controller方法前或后(取決于注釋的關鍵字是before還是after)執行切面類的方法了。
雖然通過代碼重構的技術,可以實現PHP的注釋AOP功能,但此種方法也有一些缺點,如I/O操作帶來的性能消耗。當Controller文件占用空間較大時,會對執行工作效率產生嚴重的影響。另外如果要讓注釋支持更多的功能,則需要提供更為復雜的解析方式,解析是通過正則表達式與替換、字符串查找方式來進行的,而這些操作對系統的性能也有一定影響。所以在進行解析時應盡量避免內嵌循環遍歷等操作。樣例中的代碼還有許多地方需要修改和優化,希望此篇文章能為大家起到拋磚引玉的作用。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。