PHP注釋AOP的實(shí)現(xiàn)

    AOP是時(shí)下比較流行的一種編程思想,它為程序的解耦帶來(lái)了進(jìn)一步的發(fā)展。在出現(xiàn)AOP的應(yīng)用之前,我們?nèi)绻枰诿總€(gè)程序執(zhí)行前或執(zhí)行后記錄LOG,在執(zhí)行每個(gè)代碼前進(jìn)行用戶訪問(wè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開(kāi)發(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的開(kāi)源電子商店的代碼后,受到了一點(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)的,里面還有一些問(wèn)題需要進(jìn)行優(yōu)化和完善。

    首先給出此方法的一個(gè)邏輯流程:

PHP注釋AOP的實(shí)現(xiàn)

        PHP注釋AOP的實(shí)現(xiàn)

    思想很簡(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;
    //如果沒(méi)有找到相關(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://muchs.cn/article16/ipgpgg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計(jì)、面包屑導(dǎo)航、網(wǎng)站建設(shè)定制網(wǎng)站、軟件開(kāi)發(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)

h5響應(yīng)式網(wǎng)站建設(shè)