2019年5月17日10:50:12php
前序:html
目前是想到哪寫到哪,後面有時間在整理成具體文章前端
不少時候,PHP代碼風格過於自由,致使一個項目有N多種寫法風格,有些人爲了本身認爲的技術"高",常常寫一些奇奇怪怪的寫法好比:java
例子1,一個很簡單的功能,給你寫N個回調函數,致使代碼可讀性基本爲零。,mysql
例子2,好比 if else 不寫{} 吧代碼塊包起來,不是全部的人用的ide均可以識別這種寫法,固然php的引擎解析不會有問題ios
例子3,好比爲了代碼方便 常常寫匿名函數,一層套一層,爲了寫法更簡潔,代碼沒法複用,代碼可讀性很低laravel
以上的說到的功能都是一些語法糖,不是全部開發語言都有,是特有寫法,不通用,若是隻會一種後端代碼語言,可能對這塊很難理解git
這裏會涉及到一些Java的代碼習慣,爲何PHP須要設計到java代碼規範,是由於java的不少代碼貢獻,編程思想 對整個開發社區的不可磨滅的影響github
若是習慣這種代碼習慣,你學習Java就很容易,java的生態是它成功的關鍵,如今java生態不少都支持rest api訪問php均可以使用,這個後面說redis
項目代碼是基於laravel 5.7作的demo代碼
參看PSR的編寫規範,若是徹底按照PSR來編寫代碼,確實不錯,可是,很難要求全部人的達到這個標準
中文翻譯版 http://psr.phphub.org/
已經經過的標準有1,2,3,4,6,7,這些標準都是一些不錯的標準,目前我遵循其中的一部分,爲何是一部分而不是所有,由於不能要求項目組全部人都強制這種代碼規範
一些參考規則
https://www.w3cschool.cn/phpkfbmgf/apedj4.html
其實不限制具體使用什麼ide,可是若是有可能就統一最好,否則有些代碼寫法在不一樣的ide,代碼提示和代碼追蹤是比較麻煩的,好比使用容器和回調函數的時候,只能靠本身查找源代碼
建議統一使用駝峯法,變量名也是,儘可能使用英文可讀翻譯的單詞作方法名,特別是對於不喜歡寫不少註釋的同窗
文件名稱和類型名一致,不要在一個文件寫多個類,方便代碼閱讀和管理
好比:
PurchaseInStorageService.php
代碼內容:
namespace App\Service; use App\Service\BaseService; class PurchaseInStorageService extends BaseService { public $a = 0; public static $b = 'string'; /** * * @param int $shopId * @param int $purchaseOrderId * @return type */ public static function inStorageList(int $shopId, int $purchaseOrderId) { } }
/**
*/
ide會自動吧當前方法的參數做爲註釋顯示出來,方便你編寫參數註釋
這是一種比較常見寫法,其實更規範和合理的代碼是這樣,參考代碼分層
代碼分層的好處就是代碼邏輯複用,當須要作一些邏輯彙集操做的時候,能夠直接作,不須要在吧須要作的功能所有重寫一次,直接調用之前對應的服務層
通常代碼分層
控制器層Controller
服務接口層 ServiceInterface
服務實現層 Service
服務抽象層 ServiceAbstract
模型層 Models
和控制器平級的api層
api版本控制層
計劃任務層
邏輯層
其實邏輯層和服務層有些概念部分重疊,適當選取分層
可是這裏須要注意的是PHP的抽象層和接口層是不能實例化的,至關於模板,只能繼承和實現,須要使用注入的方式來實現多態
好比如今symfony也是能夠作相似java相似的基於組件的最小化開發,相似spring boot開發的意思
還有一些通用的,可是在PHP用不是太經常使用的分層
錯誤處理層,至關於php的錯誤Handel註冊處理函數,自定義錯誤的處理
aop層,根據規範命名的控制器,或者服務層,增長一些額外附加的功能
過濾器層,對請求的過濾或者控制
這些有一些在php出現的比較少,可是都是一些編程思想的實現,通用語各類語言中
爲何要出現接口層,又出現接口實現層,這個其實有多方面考慮,好比多態,集成多種支付的時候,實現支付接口直接去寫業務邏輯,規範代碼,須要根據不一樣的類型的支付,統一代碼調用
好比
<?php namespace App\Http\ServiceInterface; interface PayServiceInterface { public function doPay(); public function callBack(); }
有些人說,若是要分細一點,就分得更細,這個主要看系統設計要求和招的開發人員的水平,分層越多,理解起來越難,寫的代碼就越多,我的建議適當的分層便可。
1,PHP項目代碼最好是無BOM的UTF-8格式,減小亂碼出現問題
2,MySQL數據庫如今建議 utf8mb4
3,關於代碼縮進,有些人習慣用空格,有些習慣用tab鍵
我的建議使用ide的格式化代碼功能,就不須要糾結制表符在不一樣系統的問題了
4,PHP的關鍵字,必須小寫,boolean值:true,false,null 也必須小寫。
下面是PHP的「關鍵字」,必須小寫:
'__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor'
其實駝峯法是java比較但願的規範,php使用下劃線也是ok的,可是駝峯法習慣以後,更換語言就無壓力
若是模塊、接口、類、方法使用了設計模式,在命名時體現出具體模式。 說明:將設計模式體如今名字中,有利於閱讀者快速理解架構設計理念。 正例:public class OrderFactory; public class LoginProxy; public class ResourceObserver;
1) 獲取單個對象的方法用 get 作前綴。 2) 獲取多個對象的方法用 list 作前綴。 3) 獲取統計值的方法用 count 作前綴。 4) 插入的方法用 save/insert 作前綴。 5) 刪除的方法用 remove/delete 作前綴。 6) 修改的方法用 update 作前綴。
5, if else 代碼塊,注意使用{}吧代碼標記起來,
6,switch case 注意case的值不要出現過於複雜的,否則會出現case匹配不上或者出現 1,2,3,4,5,6,7,8,9,10 這種會出現配置出問題,由於1和10匹配會出現問題,if else雖然代碼看起來不優雅可是問題出的少
foreach ($iterable as $key => $value) { }
foreach ($iterable as $key => &$value) { }
這兩種寫法在若是是隻簡單吧$value 修改部分的值的時候,&引用操做效率更高,由於沒有額外的中間變量,若是隻是循環作成一個新數組,無沒有實際差異
7,注意權限修飾符使用,一些特殊服務,好比支付,有些變量只能內部使用或者不能修改的時候,注意加上 final
和private,爲了安全,雖然php的是獨立進程不會出問題,可是須要養成良好的代碼習慣
8,類中的常量全部字母都必須大寫,單詞間用下劃線分隔
CONST ORDER_STATUS = 1;
,9,類的調用靜態方法效率明顯比實例化對象高,使用容器實例化對象,其實源於java的容器,由於是常駐內存因此效率很高,php也是用容器概念,其實就編程思想來講是沒問題的,
可是實際效率和靜態方法調用的差距,目前不太清楚,沒有作過詳細測試
10,請求參數安全過濾和轉義
$saleOutStorageId = !empty($Request->saleOutStorageId) ? (string) htmlspecialchars(trim($Request->saleOutStorageId), ENT_QUOTES, "UTF-8") : '';
當吧參數傳入服務方法的時候,這個注意請限定傳入類型
例如Controller
/** * 出庫單提交審覈 * @param Request $Request * @return type * */ public function submitSaleOutStorage(Request $Request) { $saleOutStorageId = !empty($Request->saleOutStorageId) ? (string) htmlspecialchars(trim($Request->saleOutStorageId), ENT_QUOTES, "UTF-8") : ''; try { SaleOutStorageService::submitSaleOutStorage(parent::$shop_id, $saleOutStorageId); return response()->json(['code' => 200, 'msg' => '操做成功']); } catch (Exception $exception) { return response()->json(['code' => 201, 'msg' => $exception->getMessage()]); } }
/** * 出庫單提交審覈 * @param int $shopId * @param int $saleOutStorageId */ public static function submitSaleOutStorage(int $shopId, int $saleOutStorageId) {
}
注意類型限定是防止轉義的一種辦法,關於返回是否須要限定類型,其實能夠限定,若是代碼要求高的話,在方法尾部加上
public static function submitSaleOutStorage(int $shopId, int $saleOutStorageId) :array{
}
注意try catch finally的使用,注意捕捉好錯誤,避免前端很差有返回
try catch finally 會用是很重要的,爲了代碼的健壯性起了很重要的做用
11,代碼邏輯拆分到原子操做
public static function weekArray() { $weekArray['0'] = 'Monday'; $weekArray['1'] = 'Tuesday'; $weekArray['2'] = 'Wednesday'; $weekArray['3'] = 'Thursday'; $weekArray['4'] = 'Friday'; $weekArray['5'] = 'Saturday'; $weekArray['6'] = 'Sunday'; return $weekArray; }
好比我要寫一個一週相關操做的,因此一週的函數就須要提出來,方便其餘人操做,不要所有寫到一個方法裏面,方便複用
好比我要作某個稍微複雜的判斷好比 判斷是不是主力合約 ,若是是16之後就是下月是主力合約,若是小於16號是本月是主力合約,返回當月主力合約月份,格式必須是1805這種的
那麼這個判斷最好拆分到具體的某個方法,而不是寫在一塊兒
下面代碼是之前的php代碼,PHP開發人員看徹底沒問題,使用下劃線最爲變量和方法的命名,可是其餘語言來,就基本兩回事了
/* * 判斷是不是主力合約 ,若是是16之後就是下月是主力合約,若是小於16號是本月是主力合約,返回當月主力合約月份,格式必須是1805這種的 */ public static function is_main_force($month = '') { if (empty($month)) { self::$msg = '月份不能爲空'; return false; } $res = self::is_current_month_over(); $date = date("y-m-01", time()); if ($res) { //若是大於16號 $date = date("ym", strtotime("$date +2 month")); } else { //小於16號 $date = date("ym", strtotime("$date +1 month")); } if ($month == $date) { return true; } else { return FALSE; } }
//當前月是否已過16號 public static function is_current_month_over() { $day = date("d", time()); if ((int) $day >= 16) { return true; } return FALSE; }
這裏還有值得注意的事情就是,PHP數值對比的時候,雖然會自動轉換,可是最好是本身手動轉換,防止數據異常轉換致使沒法捕捉的異常bug
關於代碼拆分和封裝是很重要的一個編程思想,可否寫出簡單,可讀性好的代碼的關鍵,這個不是你說你想寫成這樣就能夠寫成這樣的,須要不斷的主動去使用這種寫代碼的思惟寫出來,或者模仿成習慣
12,不要重複造輪子
好比不少優秀庫在github都有,或者自己就在框架自身裏面,不須要你本身在去寫一套,第一你臨時寫的思考的不是那麼完善,第二若是你不是按照設計模式的思惟編寫的功能,複用性不好,甚至只能你本身使用
好比laravel有不少不錯的第三方擴展庫,https://learnku.com/laravel/projects/filter/laravel-library ,若是實在沒有請必定三思而行,多去問問qq羣或者社區論壇,不要過於着急的定具體方案,說不定就能夠了解其餘不錯的方案或者庫
13,SPL的使用
spl裏面提供不少好用的東西,好比迭代器,一些數據結構,隊列,堆
這裏要着重說一下,會用和不會用spl的說明你對數據結構的認識差距很大,必定要會用
14,try catch finally
這個是一個標準代碼的,友好代碼的關鍵,習慣拋出異常,才能寫出健壯的友好的代碼
class zx { public static function test(int $tt = 0) { if (empty($tt) || $tt == 0) { throw new Exception("參數錯誤"); } return 999; } // service1 public static function loginService(string $name = '', string $password = '') { try { if (empty($name) || empty($password)) { throw new Exception("帳號密碼不能爲空"); } if ($name == 'name' && $password == 'password') { return 'OK'; } else { throw new Exception("帳號密碼錯誤"); } } catch (Exception $e) { throw new Exception($e->getMessage()); } } // service2 public static function newLoginService(string $name = '', string $password = '') { try { if (empty($name) || empty($password)) { throw new Exception("帳號密碼不能爲空"); } if ($name == 'name' && $password == 'password') { return 'OK'; } else { throw new Exception("帳號密碼錯誤"); } } catch (Exception $e) { throw $e; } } } try { echo zx::test(0); } catch (Exception $e) { echo $e->getMessage(); } finally { // 若是有流操做,或者必須操做的東西,注意在這裏關掉流,或者處理必要操做的東西,雖然php有頗有效的垃圾回收機制 }
建議service1的寫法,直接把異常扔出去 throw $e; 有可能出現提早報錯,這個不像java的throws 直接往上級調用者仍異常,有時候出現捕捉的異常不能按你的設計的返回數據格式
15,關於一些常規英文單詞的縮寫,若是你的英文水平不行,可使用翻譯工具來翻譯,縮寫的一些單詞注意必定是經常使用單詞,好比message 簡寫成msg,opreation簡寫成op,這樣變量或者方法名會短一些
好比代碼的格式化,縮短代碼的長度讓代碼讀起來不會太冗長
return ['msg'=>'OK','code'=>200,'data'=>$data];
建議不要寫成
return [ 'msg' => 'OK', 'code' => 200, 'data' => $data ];
若是常見是4-5個返回數據標準,一個簡單控制器就十幾行代碼,會讓整個文件的代碼變得特別長
16,邏輯代碼依賴層不要過多
A->B->C->D,建議不要多餘三層,由於這樣,一旦底層代碼有一層代碼是須要常常修改和變化的代碼,那你的創建在這個上面的代碼也會須要常常修改,
建議獨立,或者協商好比限定返回和參數,過程無論,只要調用和返回格式不變你的代碼就不會容易出錯,可是邏輯可能出錯,減小邏輯報錯,可是仍是會有邏輯錯誤
因此邏輯依賴層不建議過多
17,變量使用以前建議申明好類型和初始化,不要隨便操做和使用公共變量
雖然php變量類型由php引擎本身判斷,可是爲了執行代碼效率,減小因爲意外賦值或者類型改變引發的異常
關於公共變量,有些同窗喜歡在__contract裏面聲明一個類裏面絕大多數方法可能使用到的變量,其實這個是個很差的習慣,若是有人由於某些特殊需求
通過調用的時候,修改了公共變量,首先這個問題引起的問題不找查找,其次
class zx { private $resquest; public function __construct(Resquest $resquest){ $this->$resquest = $resquest; }
好比這樣,其實如今不少框架都是容器工廠初始化實例化調用類,效率並非什麼問題
建議的寫法是哪裏用到那裏調用,減小公共資源的內存的消耗,特別某些公共方法或者變量特別大的時候
18,在多重循環中,應將最忙的循環放在最內層。
示例:以下代碼效率不高。 for (row = 0; row < 100; row++) { for (col = 0; col < 5; col++) { sum += a[row][col]; } } 能夠改成以下方式,以提升效率。 for (col = 0; col < 5; col++) { for (row = 0; row < 100; row++) { sum += a[row][col]; } }
儘可能循環中不要操做數據庫或者任何流,好比文件流,字節流等
19,關於錯誤抑制符@的使用
雖然它會抑制一些特殊的錯誤,我的建議是捕捉它們而不是抑制錯誤,由於有些額外的錯誤很難經過經驗判斷出來
20,有 try 塊放到了事務代碼中,catch 異常後,若是須要回滾事務,必定要注意手動回 滾事務。
21,在使用平臺資源,譬如短信、郵件、電話、下單、支付,必須實現正確的防重放限制, 如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷、資損。
說明:如註冊時發送驗證碼到手機,若是沒有限制次數和頻率,那麼能夠利用此功能騷擾到其 它用戶,並形成短信平臺資源浪費
還有一點就是調用第三接口的時候必定要作好調用接口發送的參數,和返回參數打下日誌,雖然會不少,這個是做爲排查錯誤的重要依據
22,關於局部變量的使用
若是有多個foreach循環k,v須要使用不一樣的
若是有內部變量使用請先申明,否則有時候會出現異常
$data = []; foreach ($array as $k1 => $v1) { foreach ($v1 as $k2 => $v2) { foreach ($v2 as $k3 => $v3) { $data[] = $v3; } } }
1,表達是與否概念的字段,必須使用 is_xxx 的方式命名,數據類型是 unsigned tinyint ( 10 表示是,99 表示否)。 說明:任何字段若是爲非負數,必須是 unsigned。
正例:表達邏輯刪除的字段名 is_deleted,10 表示未刪除,99 表示刪除,不建議在數據庫0,由於不一樣的api對0這個特殊解析都有些許不一樣,特別是對接ios和java app的時候
2,表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只 出現數字。
數據庫字段名的修改代價很大,由於沒法進行預發佈,因此字段名稱須要慎重考慮。
說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。所以,數據庫 名、表名、字段名,都不容許出現任何大寫字母,避免節外生枝。
有些java開發者習慣數據庫也使用駝峯命名,這個是個習慣問題,建議使用小寫+下劃線風格
若是你在php對接數據庫,php使用了駝峯,而數據庫又使用,會使代碼看起來很奇怪,爲何代碼風格不一致,目前未發現laravel的模型能夠綁定字段 entity實體
3,【強制】小數類型爲 decimal,禁止使用 float 和 double。 說明:float 和 double 在存儲的時候,存在精度損失的問題,極可能在值的比較時,獲得不 正確的結果。
若是存儲的數據範圍超過 decimal 的範圍,建議將數據拆成整數和小數分開存儲。錢的話也是decimal,注意長度和小數點,由於小數掉後的數據會四捨五入,因此在存到數據庫以前作好四捨五入
否則會出現一分錢問題
4,varchar 是可變長字符串,不預先分配存儲空間,長度不要超過 5000,若是存儲長 度大於此值,定義字段類型爲 text,獨立出來一張表,用主鍵來對應,避免影響其它字段索 引效率。
5,表必備三字段:id, gmt_create, gmt_modified。 說明:其中 id 必爲主鍵,類型爲 unsigned bigint、單表時自增、步長爲 1。
gmt_create, gmt_modified 的類型均爲 date_time 類型,前者如今時表示主動建立,後者過去分詞表示被 動更新。
6,表的命名最好是加上「業務名稱_表的做用」。
正例:alipay_task / force_project / trade_config
7,合適的字符存儲長度,不但節約數據庫表空間、節約索引存儲,更重要的是提高檢 索速度。 正例:以下表,其中無符號值能夠避免誤存負數,且擴大了表示範圍。
對象 年齡區間 類型 字節 表示範圍
人 150 歲以內 unsigned tinyint 1 無符號值:0 到 255
龜 數百歲 unsigned smallint 2 無符號值:0 到 65535
恐龍化石 數千萬年 unsigned int 4 無符號值:0 到約 42.9 億
太陽 約 50 億年 unsigned bigint 8 無符號值:0 到約 10 的 19 次方
8,
1,超過三個表禁止 join。須要 join 的字段,數據類型必須絕對一致;多表關聯查詢時, 保證被關聯的字段須要有索引。 說明:即便雙表 join 也要注意表索引、SQL 性能。
2,在 varchar 字段上創建索引時,必須指定索引長度,不必對全字段創建索引,根據 實際文本區分度決定索引長度便可。
說明:索引的長度與區分度是一對矛盾體,通常對字符串類型數據,長度爲 20 的索引,區分 度會高達 90%以上,可使用 count(distinct left(列名, 索引長度))/count(*)的區分度 來肯定。
3,頁面搜索嚴禁左模糊或者全模糊,若是須要請走搜索引擎來解決。 說明:索引文件具備 B-Tree 的最左前綴匹配特性,若是左邊的值未肯定,那麼沒法使用此索 引。
4,若是有 order by 的場景,請注意利用索引的有序性。order by 最後的字段是組合 索引的一部分,而且放在索引組合順序的最後,
避免出現 file_sort 的狀況,影響查詢性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引中有範圍查找,那麼索引有序性沒法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 沒法排序。
5,建組合索引的時候,區分度最高的在最左邊。 正例:若是 where a=? and b=? ,a 列的幾乎接近於惟一值,那麼只須要單建 idx_a 索引即 可。
說明:存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如:where a>? and b=? 那麼即便 a 的區分度更高,也必須把 b 放在索引的最前列
6,】建立索引時避免有以下極端誤解:
1)寧濫勿缺。認爲一個查詢就須要建一個索引。
2)寧缺勿濫。認爲索引會消耗空間、嚴重拖慢更新和新增速度。
3)抵制唯一索引。認爲業務的唯一性一概須要在應用層經過「先查後插」方式解決。
7,