天天看點

opencart 業務邏輯核心controller 1.概述 2.橫向的業務分離 4.總結

近期拖鞋接手了一個opencart的二次開發項目,是以不可避免地要把opencart本身的業務邏輯看個清楚,這對于毫無php開發基礎的拖鞋來說,無疑是個蛋疼而艱巨的任務。

于是,從自學php,到把opencart的主要業務邏輯看通看懂,前前後後花了近一個星期,其中一手度娘一手gedit的辛酸自不多說,末了覺得收益良多,經驗不敢獨享,當然也怕自己忘記,遂記于此處,分享如下:

1.概述

正如标題所述,opencart的業務邏輯核心是controller類,源碼位置opencart/system/engine/controller.php。作為一個核心業務類,controller定義了opencart所有背景業務的基本特征,從動态收集頁面元素,到按照模版渲染頁面。

在橫向上,它實作了“收集”與“渲染”的業務分離,在縱向上,它采用了層層嵌套的業務組織,進而使整個頁面響應流程有條不紊地進行,清晰易用。以下也将主要從這兩個方向,解釋controller的這種設計特征。

2.橫向的業務分離

controller的橫向業務分離特征主要展現在render()函數。

<?php
    protected function render() {
		foreach ($this->children as $child) {
			$this->data[basename($child)] = $this->getChild($child);
		}
		
		if (file_exists(DIR_TEMPLATE . $this->template)) {
			extract($this->data);
			
      		ob_start();
      
	  		require(DIR_TEMPLATE . $this->template);
      
	  		$this->output = ob_get_contents();

      		ob_end_clean();
      		
			return $this->output;
    	} else {
			trigger_error('Error: Could not load template ' . DIR_TEMPLATE . $this->template . '!');
			exit();				
    	}
    }
?>
           

從上面的代碼可以看到,foreach循環完成了目前controller頁面元素的動态收集,它通過周遊目前controller所屬的children(其實就是一個controller數組,下文有述,此處暫表不提),并用getchild()傳回每個child的執行結果,然後把這些執行結果放在一個名叫data的數組中,完成收集功能。

foreach之後,緊接一個if else,判斷目前controller所需模闆是否存在,不存在則輸出錯誤資訊,此處我們主要看判斷存在的支路,它完成了讀取模闆,并按照模闆渲染頁面的功能。

首先是extract()這個函數,度娘可知它是個php預定義函數,作用是把數組元素從鍵值形式轉化成變量形式後,把它們統統置于目前的符号表中,舉例來說,就是把數組中的a=>b轉成$a=b後抛出,進而能使後面的代碼使用$a(可視作臨時變量,局限于目前作用域)。

從代碼可知,作者正是通過使用這個神奇的函數,進而輕松地把之前存放在data中的執行結果“解包”出來,供後面require進來的模闆“使用”。值得一提的是,這種“使用”過程事實上是被動的,是需要納入模闆設計者考慮的,關于這點我們随便打開一個預設模闆目錄(catalog/view/theme/default/template)下的模闆就可以大概了解到,從data提取出的這些變量值,事實上恰恰确定了模闆中所有用php書寫的動态部分,也正因如此,作者才能用這麼簡單的幾句代碼就完成了controller的渲染。

此外,還有一個地方需要注意,關于ob_start(),ob_get_contents()與ob_end_clean()。ob其實就是緩沖區,ob_start()打開緩沖區後,此後代碼的所有輸出将全部放在這個使用者不可見的緩沖區中,直到ob_end_clean()的出現,關閉并清除緩沖區。作者在此處用緩沖區存放渲染結果,然後再用ob_get_contents()傳回緩沖區的輸出内容到controller的output中,目的就是令渲染過程完成在緩沖區中,友善放于一個變量裡,封裝傳回渲染結果。這對于controller縱向的層層嵌套是有重要意義的,下面也将述及這一點。

3.縱向的業務組織

controller的縱向業務組織特征是 render() 和 getchild() 的聯合作用。

上文已說了render(),此處重點說getchild(),下面将先理清其具體實作:

<?php
	protected function getChild($child, $args = array()) {
		$action = new Action($child, $args);
	
		if (file_exists($action->getFile())) {
			require_once($action->getFile());

			$class = $action->getClass();

			$controller = new $class($this->registry);

			$controller->{$action->getMethod()}($action->getArgs());
			
			return $controller->output;
		} else {
			trigger_error('Error: Could not load controller ' . $child . '!');
			exit();					
		}		
	}
?>
           

上文提過,這個函數将傳回一個controller的output,條件是它需要一個child參數和一個可預設的數組參數。這個child可以把它看成是一個controller子類的檔案路徑,這從它能作為action的構造參數就可以看出。

關于action,在這裡我們先轉過頭去看看它的代碼實作,它是一個同樣放在engine目錄下的類,其意義就是把一個controller子類的檔案路徑(也可能不是檔案路徑,而是一個精确到需調用函數的‘函數路徑’),解釋封裝成一個指明了類名和需調用函數等“動作”資訊的對象。當然,action的需調用函數通常會預設,也就是當傳入的路徑參數的确是一個純粹的檔案路徑時,action的需調用函數會預設指定為一個名為index的函數,一個所有controller子類都“約定”會有的函數。

關于index函數,我們先不管它的具體實作,下文會有繼續有相關展開,現在隻需知道它是一個通知controller子類對象準備相關資訊,然後進行渲染工作的函數即可(好吧,其實這就是它的全部意義了……)。

有了這個概念,我們就可以回頭看getchild()了。很明顯的,new Action之後的一堆東西其實就是調用了controller子類對象的index,通知其進行渲染工作,并傳回結果。

說完getchild,下面就可以展開對controller縱向特征的分析了:

首先,要說明的一點是,判斷一個過程是否是嵌套機制,拖鞋習慣于關注兩點,一是任務的下派,二是結果的傳回,隻要理清這兩點,就可以很容易判斷過程是否存在嵌套。

而在controller中,任務下派的過程展現在調用render時,周遊controller對象所屬的每個child,并用getchild通知每個child執行工作,又因為child實際上是controller子類的對象,這個對象實作了一個準備了相關資料并調用render的index函數,是以這種通知child執行的工作事實上是層層下派的,由此,任務下派過程成立。

結果傳回的過程則展現在傳回getchild後,所有傳回結果都存放在data數組中,而data在render中被用extract“解包”後,與模闆共同作用渲染頁面,然後再存放在output中。在這裡考慮上文任務下派的層次關系,可以假定這個調用render的對象實際上是個controller子類對象,它的渲染任務是上一級controller分派下來的,那麼,這個render應該是在這個子類對象的index中被調用,而index則在上一級controller的getchild中被調用。于是,在執行完index後,上一級controller的getchild把下一級的controller子類對象的output作為傳回,進而完成逐級傳回,由此,結果傳回過程成立。

4.總結

要了解opencart的業務邏輯,controller的具體實作是繞不開的一道坎,而要了解controller的具體實作,拖鞋覺得從上面兩個角度分析是比較好懂的,至少算是一種把靈活但淩亂的php子產品化清晰化的努力,是以,才有了這篇博文。