最近優化php項目,記錄下經驗,直接上干活。。。
php在公司項目中主要用於頁面展現,前端有個view,view向後端的service請求數據,數據的傳輸格式是json。下面看優化前的service的代碼:
[php]
<?php
require_once('../../../global.php');
require_once(INCLUDE_PATH . '/discache/CacherManager.php');
require_once(INCLUDE_PATH.'/oracle_oci.php');
require_once(INCLUDE_PATH.'/caihui/cwsd.php');
header('Content-type: text/plain; charset=utf-8');
$max_age = isset($_GET['max-age']) ? $_GET['max-age']*1 : 15*60;
if($max_age < 30) {
$max_age = 30;
}
header('Cache-Control: max-age='.$max_age);
// 通過將url進行hash作為緩沖key
$url = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$url_hash = md5($url);
//echo "/finance/hs/marketdata/segment/${url_hash}.json";
if (!CacherManager::cachePageStart(CACHER_MONGO, "/finance/hs/marketdata/segment/${url_hash}.json", 60*60)) {
// 查詢條件
$page = isset($_GET['page']) ? $_GET['page']*1 : 0;
$count = isset($_GET['count']) ? $_GET['count']*1 : 30;
$type = isset($_GET['type']) ? $_GET['type'] : 'query';
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'symbol';
$order = isset($_GET['order']) ? $_GET['order'] : 'desc';
$callback = isset($_GET['callback']) ? $_GET['callback'] : null;
$fieldsstring = isset($_GET['fields']) ? $_GET['fields'] : null;
$querystring = isset($_GET['query']) ? $_GET['query'] : null;
$symbol=isset($_GET['symbol'])?$_GET['symbol']:'';
$date=isset($_GET['date'])?$_GET['date']:'';
if ($type == 'query') {
$queryObj = preg_split('/:|;/', $querystring, -1);
for($i=0; $i<count($queryObj); $i=$i+2){
if(emptyempty($queryObj[$i])) continue;
if($queryObj[$i]=='symbol'){
$symbol = $queryObj[$i+1];
}
if($queryObj[$i]=='date'){
$date = $queryObj[$i+1];
}
}
}
// 查詢列表
$oci = ntes_get_caihui_oci();
$stocklist = array();
$cwsd = new namespace\dao\caihui\Cwsd($oci);
$stockcurror = $cwsd->getCznlList($symbol,$date,$sort,$order,$count*($page),$count);
$sumrecords=$cwsd->getRecordCount($symbol,$date);
$i=0;
//var_dump($symbol,$date,$sort,$order,$count*($page),$count);
foreach($stockcurror as $item){
$item['RSMFRATIO1422']=isset($item['RSMFRATIO1422'])?number_format($item['RSMFRATIO1422'],2).'%':'--';
$item['RSMFRATIO1822']=isset($item['RSMFRATIO1822'])?number_format($item['RSMFRATIO1822'],2).'%':'--';
$item['RSMFRATIO22']=isset($item['RSMFRATIO22'])?number_format($item['RSMFRATIO22'],2).'%':'--';
$item['RSMFRATIO10']=isset($item['RSMFRATIO10'])?number_format($item['RSMFRATIO10'],2):'--';
$item['RSMFRATIO12']=isset($item['RSMFRATIO12'])?number_format($item['RSMFRATIO12'],2):'--';
$item['RSMFRATIO4']=isset($item['RSMFRATIO4'])?number_format($item['RSMFRATIO4'],2):'--';
$item['RSMFRATIO18']=isset($item['RSMFRATIO18'])?number_format($item['RSMFRATIO18'],2):'--';
$item['RSMFRATIO14']=isset($item['RSMFRATIO14'])?number_format($item['RSMFRATIO14'],2):'--';
$item['CODE']=$item['EXCHANGE'].$item['SYMBOL'];
//$item['REPORTDATE']=isset($item['REPORTDATE'])?$item['REPORTDATE']:'--';
$stocklist[$i] = $item;
$i=$i+1;
}
// 輸出結果
$result = array();
// 頁碼page、每頁數量count、結果總數total、分頁數pagecount、結果列表list
$result['page'] = $page;
$result['count'] = $count;
$result['order'] = $order;
$result['total'] = $i;//$stockcurror->count();
$result['pagecount'] = ceil($sumrecords['SUMRECORD']/$count);
$result['time'] = date('Y-m-d H:i:s');
$result['list'] = $stocklist;
if(emptyempty($callback)){
echo json_encode($result);
}else{
echo $callback.'('.json_encode($result).');';
}
CacherManager::cachePageEnd();
}
?>
下面看一下這個service具體完成的功能:
1. 6-16行,准備緩存參數,開啟緩存。
2. 19-41行,提取請求參數。
3. 44-49行,連接、查詢數據庫。
4. 50-67行,將數據庫查詢結果放入數組。
5. 71-84行,准備json數據。
6. 86-87行,關閉緩存。
如果只看這一個文件,存在的問題有:
1. 19-86行,沒有縮進。
2. 44行,每次請求都會重新連接數據庫。
3. 53-61行,重復的邏輯,可以提取為一個函數,然後通過迭代完成。
如果大部分後端Service都采用這個結構,那麼問題就是所有的Service都需要經過:開啟緩存,取參,獲取數據,json轉化,關閉緩存這一系列的過程。而在所有過程中,除了獲取數據的邏輯,其他的過程都是一樣的。在代碼中存在著大量的重復邏輯,甚至給人一種“復制-粘貼”的感覺,這嚴重的違背了DRY原則(Don't Repeat Yourself)。所以,這裡需要運用面向對象的思想對其重構。而在我重構的過程中,腦海中始終謹記著一個原則——封裝變化原則。所謂封裝變化,就是區分系統中不變的和可變的,將可變的進行封裝,這樣可以很好過應對變化。
通過上面的分析,只有獲取數據的邏輯是變化的,其他的邏輯是不變的。所以需要對獲取數據的邏輯進行封裝,具體的封裝方式可以采用繼承或組合。我采用的是繼承的方式,首先將service的處理過程抽象為:
service(){
startCache();
getParam();
getData(); // 抽象方法,由子類實現
toJson();
closeCache();
}
抽象出ServiceBase類,由子類繼承,實現相應的獲取數據的邏輯,子類不需要處理其他的取參、緩存等邏輯,這些都被ServiceBase類處理了。
[php]
abstract class ServiceBase {
public function __construct($cache_path, $cache_type, $max_age, $age_explore) {
// 獲取請求參數
$this->page = $this->getQueryParamDefault('page', 0, INT);
// 省略其他的獲取參數的邏輯
……
// 生成響應
$this->response();
}
/**
*
* 子類實現,返回數組格式的數據
*/
abstract protected function data();
/**
*
* 子類實現,返回所有數據的總數
*/
abstract protected function total();
private function cache() {
$url = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$url_hash = md5($url);
$key = $this->cache_path.$url_hash.'.json';
if(!CacherManager::cachePageStart($this->cache_type, $key, $this->age_cache)){
$this->no_cache();
CacherManager::cachePageEnd();
}
}
private function no_cache(){
$data = $this->data();
$total = $this->total();
$this->send_data($data, $total);
}
private function send_data($data, $total){
// 進行json轉化,省略具體代碼
}
private function response() {
header('Content-type: text/plain; charset=utf-8');
header('Cache-Control: max-age='.$this->age_explore);
if($this->cache_type == NONE || self::$enable_cache == false){
$this->no_cache();
}else{
$this->cache();
}
}
}
這就是各個service的抽象父類,有兩個抽象方法data和total,data是返回數組格式的數據,tatol是由於分頁加入的。具體的service只要繼承ServiceBase並實現data和total方法即可,其他的邏輯都是復用的父類的。實際上,優化後的ServiceBase是使用了模板方法模式(Template Method),父類定義算法處理的流程(service的處理過程),子類實現某個具體變化的步驟(具體service的獲取數據的邏輯)。通過使用模板方法模式可以保證步驟的變化對於客戶端是透明的,並且可以復用父類中的邏輯。
下面是上述php采用ServiceBase後的代碼:
[php]
class CWSDService extends ServiceBase{
function __construct(){
parent::__construct();
$oci = ntes_get_caihui_oci();
$this->$cwsd = new namespace\dao\caihui\Cwsd($oci);
}
public function data(){
$stocklist = array();
$stockcurror = $this->cwsd->getCznlList($this->query_obj['symbol'],
$this->query_obj['symbol'], $sort, $order, $count*($page), $count);
$filter_list = array('RSMFRATIO1422', 'RSMFRATIO1822', 'RSMFRATIO22',
'RSMFRATIO10', 'RSMFRATIO12', 'RSMFRATIO4', 'RSMFRATIO18',
'RSMFRATIO14');
$i=0;
foreach($stockcurror as $item){
foreach($filter_list as $k)
$this->filter($item, $k);
$item['CODE']=$item['EXCHANGE'].$item['SYMBOL'];
$stocklist[$i] = $item;
$i=$i+1;
}
return $stocklist;
}
public function total(){
return $sumrecords=$this->cwsd->getRecordCount($this->query_obj['symbol'],
$this->query_obj['symbol']);
}
private function filter($item, $k){
isset($item[$k])?number_format($item[$k],2).'%':'--';
}
}
new CWSDService('/finance/hs/realtimedata/market/ab', MONGO, 30, 30);
代碼量從87減少到32行,是因為大部分的邏輯都由父類完成,具體service只需要關注自己的業務邏輯就可以了。通過上面代碼可以看出繼承可以實現代碼復用,多個子類中的相同的邏輯可以提取到父類中達到復用的目的;同時,繼承也增加了父類和子類之間的耦合性,這也就是組合由於繼承的方面,如果這個例子采用組合來封裝變化,則具體的實現就是策略模式,將具體獲取數據的邏輯看成是策略,不同的service就是不同的策略,由於時間原因,不再贅述。。。
摘自 chosen0ne的專欄