一. 說明
以下內(nèi)容大部分引用Laravel China社區(qū)的文章 - 分享下團(tuán)隊(duì)的開發(fā)規(guī)范 ——《Laravel 項(xiàng)目開發(fā)規(guī)范》叛复。
相對(duì)而言眉枕,上面引用的文章的規(guī)范更加嚴(yán)格舍沙,但考慮到目前的情況,會(huì)適當(dāng)?shù)貙?duì)一些規(guī)范進(jìn)行更改和增刪贵试。
二. 目的
暫無
三. 優(yōu)點(diǎn)
規(guī)范有一下優(yōu)點(diǎn):
- 高效編碼 - 避免了過多的選擇造成的『決策時(shí)間』浪費(fèi)糟港;
- 風(fēng)格統(tǒng)一 - 最大程度統(tǒng)一了開發(fā)團(tuán)隊(duì)成員代碼書寫風(fēng)格和思路攀操,代碼閱讀起來如出一轍;
- 減少錯(cuò)誤 - 減小初級(jí)工程師的犯錯(cuò)幾率秸抚。
四. 開發(fā)哲學(xué)
- DRY –「Don't Repeat Yourself」不寫重復(fù)的邏輯代碼速和;
- 約定俗成 - 「Convention Over Configuration」歹垫,優(yōu)先選擇框架提倡的做法,不過度配置颠放;
- KISS - 「Keep it Simple, Stupid」提倡簡(jiǎn)單易讀的代碼排惨,不寫高深、晦澀難懂的代碼碰凶,不過度設(shè)計(jì)暮芭;
- 主廚精選 - 讓有經(jīng)驗(yàn)的人來為你選擇方案,不獨(dú)創(chuàng)方案痒留;
- 官方提倡 - 優(yōu)先選擇官方推崇的方案谴麦。
五. 設(shè)計(jì)理念
以下是一些優(yōu)秀的『程序設(shè)計(jì)理念』:
- MVC - Model, View, Controller 蠢沿,以 MVC 為核心伸头,嚴(yán)格控制 Controller 的可讀性和代碼行數(shù);
- Restful - 利用『資源化概念』和標(biāo)準(zhǔn)的 HTTP 動(dòng)詞來組織你的程序舷蟀;
在此規(guī)范中恤磷,我們會(huì)將使用這兩套理念作為程序設(shè)計(jì)基礎(chǔ)。這些設(shè)計(jì)理念為我們?cè)O(shè)計(jì)程序提供了依據(jù)野宜,遵循這些理念扫步,能讓程序變得清晰易讀。
六. 能愿動(dòng)詞
為了避免歧義匈子,文檔大量使用了「能愿動(dòng)詞」河胎,對(duì)應(yīng)的解釋如下:
- 必須(Must) - 只能這樣子做,請(qǐng)無條件遵循虎敦,沒有別的選項(xiàng)游岳;
- 絕不(Must Not)- 嚴(yán)令禁止,在任何情況下都不能這樣做其徙;
- 應(yīng)該(Should) - 強(qiáng)烈建議這樣做胚迫,但是不強(qiáng)求;
- 不應(yīng)該(Should Not) - 強(qiáng)烈建議不這樣做唾那,但是不強(qiáng)求访锻;
- 可以(May) - 選擇性高一點(diǎn),在這個(gè)文檔內(nèi)闹获,此詞語使用較少期犬;
七. 關(guān)于Laravel版本的選擇
選擇Laravel版本時(shí),應(yīng)該 優(yōu)先選擇LTS版本避诽,除非有特殊原因龟虎,如生產(chǎn)服務(wù)器的PHP版本不是PHP7以上,而是PHP5.*茎用,且為了穩(wěn)定不升級(jí)到PHP7遣总,那么 可以 考慮使用上一個(gè)版本的發(fā)行版睬罗。
比如Laravel 5.5是最新的LTS但是只支持PHP7以上,那么 可以 考慮使用Laravel 5.4旭斥。當(dāng)然比較可以使用Laravel 5.1 LTS版本容达,但是該版本比較舊,沒有新版本的一些新特性垂券。
請(qǐng)使用以下命令來創(chuàng)建指定版本的 Laravel 項(xiàng)目:
composer create-project laravel/laravel project-name --prefer-dist "5.5.*"
絕不 也禁止使用復(fù)制粘貼項(xiàng)目文件的方式來創(chuàng)建項(xiàng)目花盐。
八. 環(huán)境說明
一般情況下,一個(gè)項(xiàng)目 應(yīng)該 有以下三個(gè)基本的項(xiàng)目環(huán)境:
- Local - 開發(fā)環(huán)境
- Staging - 線上測(cè)試環(huán)境菇爪,對(duì)應(yīng)git的test分支
- Production - 線上生產(chǎn)環(huán)境算芯,對(duì)應(yīng)git的master分支
九. git分支
在創(chuàng)建git倉(cāng)庫(kù)后,建議最好分開三個(gè)分支
- 主分支 - master凳宙,對(duì)應(yīng)開發(fā)環(huán)境
- 測(cè)試分支 - test熙揍,對(duì)應(yīng)線上測(cè)試環(huán)境
- 開發(fā)分支 - develop,對(duì)應(yīng)線上生產(chǎn)環(huán)境
所有功能都是從develop分支新建分支氏涩,按功能模塊命名
新的功能模塊届囚,使用
features/功能名稱
來命名修復(fù)bug,使用
fix/bug名稱
來命名功能開發(fā)后是尖,合并到develop
開發(fā)測(cè)試通過后意系,將develop合并到test分支
測(cè)試環(huán)境功能測(cè)試通過后,將test合并到master
十. 配置信息與環(huán)境變量
在 Laravel 中有以下幾種方法:
- 硬代碼饺汹,直接寫死蛔添。- ? 可維護(hù)性低
- 寫死在
config/app.php
文件中。 - ? 無法區(qū)分環(huán)境進(jìn)行配置 - 存儲(chǔ)于
.env
文件中兜辞,使用env()
方法直接讀取迎瞧。 - ? 雖然解決了環(huán)境變量問題但是不推薦 - 存儲(chǔ)在
.env
和config/app.php
文件中,然后使用config()
函數(shù)來讀取弦疮。- ? 最佳實(shí)踐
第一種方法是最古老的方法夹攒,代碼可維護(hù)性極低,一旦域名變更就只能全局替換胁塞。
第二種方法無法區(qū)分環(huán)境咏尝,例如本地使用開發(fā)環(huán)境域名測(cè)試,線上才是正式的域名啸罢。
第三種方法雖然解決了環(huán)境變量的問題编检,并且也具備一定的靈活性,但是不夠靈活扰才,假如你的網(wǎng)站流量巨大允懂,需要配置幾個(gè)域名,使其在加載靜態(tài)資源時(shí)隨機(jī)支配域名衩匣,這種做法就無法滿足需求了蕾总。
第四種方法既支持環(huán)境變量粥航,又具備極高的靈活性,假如遇到同樣的多域名隨機(jī)問題生百,你只需要寫一個(gè)輔助方法递雀,然后在 config/app.php
中調(diào)用即可,不需要?jiǎng)拥饺魏我恍袠I(yè)務(wù)邏輯代碼蚀浆。
代碼示例
.env
文件中設(shè)置:
DOMAIN=018eighteen.test
config/app.php
文件中設(shè)置:
'domain' => env('DOMAIN', '018eighteen.com'),
程序中兩種獲取 相同配置 的方法:
env('DOMAIN')
config('app.domain')
在此統(tǒng)一規(guī)定:所有程序配置信息 必須 通過 config()
來讀取缀程,所有的 .env
配置信息 必須 通過 config()
來讀取,絕不 在配置文件以外的范圍使用 env()
市俊。
這樣做主要有以下幾個(gè)優(yōu)勢(shì):
- 定義分明杨凑,
config()
是配置信息,env()
只是用來區(qū)分不同環(huán)境摆昧; - 統(tǒng)一放置于
config
中還可以利用框架的 配置信息緩存功能 來提高運(yùn)行效率撩满; - 代碼健壯性,
config()
在env()
之上多出來一個(gè)抽象層据忘,會(huì)使代碼更加健壯鹦牛,更加靈活。
十一. 路由器
1. 路由閉包
絕不 在路由配置文件里書寫『閉包路由』或者其他業(yè)務(wù)邏輯代碼勇吊,因?yàn)橐坏┦褂脤o法使用 路由緩存 。
路由器要保持干凈整潔窍仰,絕不 放置除路由配置以外的其他程序邏輯汉规。
2. Restful 路由
必須 優(yōu)先使用 Restful 路由,配合資源控制器使用驹吮,見 文檔针史。
超出 Restful 路由的,應(yīng)該 模仿上圖的方式來定義路由碟狞。
3. resource 方法正確使用
一般資源路由定義:
Route::resource('photos', 'PhotosController');
等于以下路由定義:
Route::get('/photos', 'PhotosController@index')->name('photos.index');
Route::get('/photos/create', 'PhotosController@create')->name('photos.create');
Route::post('/photos', 'PhotosController@store')->name('photos.store');
Route::get('/photos/{photo}', 'PhotosController@show')->name('photos.show');
Route::get('/photos/{photo}/edit', 'PhotosController@edit')->name('photos.edit');
Route::put('/photos/{photo}', 'PhotosController@update')->name('photos.update');
Route::delete('/photos/{photo}', 'PhotosController@destroy')->name('photos.destroy');
使用 resource
方法時(shí)啄枕,如果僅使用到部分路由,必須 使用 only
列出所有可用路由:
Route::resource('photos', 'PhotosController', ['only' => ['index', 'show']]);
絕不 使用 except
族沃,因?yàn)?only
相當(dāng)于白名單频祝,相對(duì)于 except
更加直觀。路由使用白名單有利于養(yǎng)成『安全習(xí)慣』脆淹。
4. 單數(shù) or 復(fù)數(shù)常空?
資源路由路由 URI 必須 使用復(fù)數(shù)形式,如:
/photos/create
/photos/{photo}
錯(cuò)誤的例子如:
/photo/create
/photo/{photo}
5. 路由命名
除了 resource
資源路由以外盖溺,其他所有路由都 必須 使用 name
方法進(jìn)行命名漓糙。
必須 使用『資源前綴』作為命名規(guī)范,如下的 users.follow
烘嘱,資源前綴的值是 users.
:
Route::post('users/{id}/follow', 'UsersController@follow')->name('users.follow');
6. 獲取 URL
獲取 URL 必須 遵循以下優(yōu)先級(jí):
$model->link()
-
route
方法 -
url
方法
在 Model 中創(chuàng)建 link()
方法:
public function link($params = [])
{
$params = array_merge([$this->id], $params);
return route('models.show', $params);
}
所有單個(gè)模型數(shù)據(jù)鏈接使用:
$model->link();
// 或者添加參數(shù)
$model->link($params = ['source' => 'list'])
『?jiǎn)蝹€(gè)模型 URI』經(jīng)常會(huì)發(fā)生變化昆禽,這樣做將會(huì)讓程序更加靈活蝗蛙。
除了『?jiǎn)蝹€(gè)模型 URI』,其他路由 必須 使用 route
來獲取 URL(這也是目前使用次數(shù)最多的方法):
$url = route('profile', ['id' => 1]);
無法使用 route
的情況下醉鳖,可以 使用 url
方法來獲取 URL:
url('profile', [1]);
十二. 數(shù)據(jù)模型
1. 放置位置
所有的數(shù)據(jù)模型文件歼郭,都 必須 存放在:app/Models/
文件夾中。
命名空間:
namespace App\Models;
2. 使用基類
所有的 Eloquent 數(shù)據(jù)模型 都 必須 繼承統(tǒng)一的基類 App/Models/Model
辐棒,此基類存放位置為 /app/Models/Model.php
病曾,內(nèi)容參考以下:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Model extends EloquentModel
{
public function scopeRecent($query)
{
return $query->orderBy('created_at', 'desc');
}
}
以 Photo 數(shù)據(jù)模型作為例子繼承 Model 基類:
<?php
namespace App\Models;
class Photo extends Model
{
protected $fillable = ['id', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
3. 命名規(guī)范[#]
數(shù)據(jù)模型相關(guān)的命名規(guī)范:
- 數(shù)據(jù)模型類名
必須
為「單數(shù)」, 如:App\Models\Photo
- 類文件名
必須
為「單數(shù)」,如:app/Models/Photo.php
- 數(shù)據(jù)庫(kù)表名字
必須
為「復(fù)數(shù)」漾根,多個(gè)單詞情況下使用「Snake Case」 如:photos
,my_photos
(注:目前由于和其他團(tuán)隊(duì)合作開發(fā)泰涂,所以這一條規(guī)范暫時(shí)不硬性要求) - 數(shù)據(jù)庫(kù)表遷移名字
必須
為「復(fù)數(shù)」,如:2014_08_08_234417_create_photos_table.php
- 數(shù)據(jù)填充文件名
必須
為「復(fù)數(shù)」辐怕,如:PhotosTableSeeder.php
- 數(shù)據(jù)庫(kù)字段名
必須
為「Snake Case」逼蒙,如:view_count
,is_vip
- 數(shù)據(jù)庫(kù)表主鍵
必須
為「id」(注:這條規(guī)范一定要嚴(yán)格執(zhí)行,避免像018server的prouct
表一樣出現(xiàn)product_id
這樣的主鍵) - 數(shù)據(jù)庫(kù)表外鍵
必須
為「resource_id」寄疏,如:user_id
,post_id
- 數(shù)據(jù)模型變量
必須
為「resource_id」是牢,如:$user_id
,$post_id
4. 利用 Trait 來擴(kuò)展數(shù)據(jù)模型
有時(shí)候數(shù)據(jù)模型里的代碼會(huì)變得很臃腫,應(yīng)該 利用 Trait 來精簡(jiǎn)邏輯代碼量陕截,提高可讀性驳棱,類似于 Ruby China 源碼。
借鑒于 Rails 的設(shè)計(jì)理念:「Fat Models, Skinny Controllers」农曲。
存放于文件夾:app/Models/Traits
文件夾中社搅。
5. Repository
在分享下團(tuán)隊(duì)的開發(fā)規(guī)范 ——《Laravel 項(xiàng)目開發(fā)規(guī)范》 提出不適用 Repository
模式進(jìn)行開發(fā),但是考慮到隨著功能越來越多乳规,不適用 Repository
會(huì)使得控制器越來越臃腫形葬,有些代碼也會(huì)不停地重復(fù)寫,另外以后有可能需要編寫單元測(cè)試暮的,所以最后還是決定啟用 Repository
笙以。
6. 關(guān)于 SQL 文件
- 絕不 使用命令行或者 PHPMyAdmin 直接創(chuàng)建索引或表。必須 使用 數(shù)據(jù)庫(kù)遷移 去創(chuàng)建表結(jié)構(gòu)冻辩,并提交版本控制器中猖腕;
- 絕不 為了共享對(duì)數(shù)據(jù)庫(kù)更改就直接導(dǎo)出 SQL,所有修改都 必須 使用 數(shù)據(jù)庫(kù)遷移 微猖,并提交版本控制器中谈息;
- 絕不 直接向數(shù)據(jù)庫(kù)手動(dòng)寫入偽造的測(cè)試數(shù)據(jù)。必須 使用 數(shù)據(jù)填充 來插入假數(shù)據(jù)凛剥,并提交版本控制器中侠仇。
考慮到可能會(huì)和其他團(tuán)隊(duì)合作開發(fā),所以具體還是根據(jù)團(tuán)隊(duì)的協(xié)定而定。但是如果是自己團(tuán)隊(duì)開發(fā)的話逻炊,必須嚴(yán)格按照以上標(biāo)準(zhǔn)互亮。
十三. 控制器
1. 資源控制器
必須 優(yōu)先使用 Restful 資源控制器 。
2. 單數(shù) or 復(fù)數(shù)余素?
必須 使用資源的復(fù)數(shù)形式豹休,如:
- 類名:PhotosController
- 文件名:PhotosController.php
錯(cuò)誤的例子:
- 類名:PhotoController
- 文件名:PhotoController.php
3. 保持短小精煉
必須 保持控制器文件代碼行數(shù)最小化,還有可讀性桨吊。
- 不應(yīng)該 為「方法」書寫注釋威根,這要求方法取名要足夠合理,不需要過多注釋视乐;
-
應(yīng)該 為一些復(fù)雜的邏輯代碼塊書寫注釋洛搀,主要介紹產(chǎn)品邏輯 -
為什么要這么做。
佑淀; -
不應(yīng)該 在控制器中書寫「私有方法」留美,控制器里
應(yīng)該
只存放「路由動(dòng)作方法」; - 絕不 遺留「死方法」伸刃,就是沒有用到的方法谎砾,控制器里的所有方法,都應(yīng)該被使用到捧颅,否則應(yīng)該刪除景图;
- 絕不 在控制器里批量注釋掉代碼,無用的邏輯代碼就必須清除掉隘道。
- 應(yīng)該 創(chuàng)建Service層建立對(duì)應(yīng)的Service類症歇,以實(shí)現(xiàn)控制器對(duì)應(yīng)的邏輯,見下放關(guān)于Service
十四. Service
在上面提過決定啟用 Repository
谭梗,Repository
主要是實(shí)現(xiàn)對(duì)model的增刪改查。
而 Service
則是介于 Controller
與 Repository
之間宛蚓,是對(duì) Controller
業(yè)務(wù)邏輯的實(shí)現(xiàn)激捏,實(shí)現(xiàn)過程中,通過 Repository
來對(duì)數(shù)據(jù)進(jìn)行操作凄吏。
1. 創(chuàng)建 Service
文件夾
首先我們需要在 app
文件夾創(chuàng)建自己 Service
文件夾 services
远舅,然后文件夾的每一個(gè)文件都要設(shè)置相應(yīng)的命名空間。
2. 創(chuàng)建對(duì)應(yīng)的 Service
類
PostsController.php
<?php
namespace App\Controllers;
use App\Http\Controllers\Controller;
use App\Services\PostsService;
class PostsController extend Controller{
private $postsService;
public function __construct (PostsService $posts) {
$this->postsService = $posts;
}
public function addArticle (Request $request) {
return $this->postsService->addArticle ($request->all());
}
}
PostsService.php
:
<?php
namespace App\Services;
class PostsService{
public function addArticle ($data) {
//To add a article...
}
}
十五. 視圖
在不進(jìn)行前后端分離的情況下痕钢,請(qǐng)使用視圖
1. 優(yōu)先使用 Blade
視圖文件 必須 優(yōu)先考慮使用 .blade.php
后綴來指定使用 Blade 模板引擎图柏。
2. 保持目錄清晰
- layouts - 頁(yè)面布局文件 必須 放置于此目錄下;
- common - 存放頁(yè)面通用元素任连;
- pages - 簡(jiǎn)單的頁(yè)面存放文件夾蚤吹,如:about、contact 等;
- resources - 對(duì)應(yīng) Restful 路由的資源路徑名稱裁着,以 URI
photos/create
為例繁涂,對(duì)應(yīng)create.blade.php
文件,存放在文件夾photos
下二驰。
必須 避免在 resources/views
目錄下直接放置視圖文件扔罪。
3. 局部視圖
局部視圖文件 必須 使用 _
前綴來命名,如:photos/_upload_form.blade.php
4. 視圖命名要釋義
為了和 Restful 路由器和資源控制器保持一致桶雀,視圖命名也 必須 使用資源視圖的命名方式矿酵。以 photos
為例:
-
photos/index.blade.php
- 內(nèi)容列表視圖
- 對(duì)應(yīng)路由器
/photos
,命名photos.index
- 控制器方法
PhotosController@index
-
photos/show.blade.php
- 單個(gè)內(nèi)容視圖
- 對(duì)應(yīng)路由器
/photos/{id}
矗积,命名photos.show
- 控制器方法
PhotosController@show
-
photos/create.blade.php
- 內(nèi)容創(chuàng)建視圖
- 對(duì)應(yīng)路由器
/photos/create
全肮,命名photos.create
- 控制器方法
PhotosController@create
-
photos/edit.blade.php
- 內(nèi)容編輯的視圖
- 對(duì)應(yīng)路由器
/photos/edit
,命名photos.edit
- 控制器方法
PhotosController@edit
5. create_and_edit
視圖
很多情況下漠魏,創(chuàng)建和編輯視圖里的頁(yè)面結(jié)構(gòu)接近相似倔矾,在這種情況下,應(yīng)該 使用 create_and_edit
視圖柱锹。以 photos
為例:
-
PhotosController@create
- 對(duì)應(yīng)視圖:/photos/create_and_edit.blade.php
-
PhotosController@edit
- 對(duì)應(yīng) 視圖:/photos/create_and_edit.blade.php
這樣一來哪自,通常情況下,一個(gè)完整的 photos
資源對(duì)應(yīng)的視圖文件為以下:
├── photos
│ ├── create_and_edit.blade.php
│ ├── index.blade.php
│ └── show.blade.php
十六. 表單驗(yàn)證
1. 表單請(qǐng)求驗(yàn)證類
必須 使用 表單請(qǐng)求 - FormRequest 類 來處理控制器里的表單驗(yàn)證。
2. 使用基類
所有 FormRequest 表驗(yàn)證類 必須 繼承 app/Http/Requests/Request.php
基類睦擂⊥荻常基類文件如下:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Request extends FormRequest
{
public function authorize()
{
// Using policy for Authorization
return true;
}
}
3. 驗(yàn)證類命名
FormRequest 表驗(yàn)證類 必須 遵循 資源路由 方式進(jìn)行命名,photos
對(duì)應(yīng) app/Http/Requests/PhotoRequest.php
胧华。
4. 類文件參考
FormRequest 表驗(yàn)證類文件請(qǐng)參考以下:
<?php
namespace App\Http\Requests;
class PhotoRequest extends Request
{
public function rules()
{
switch($this->method())
{
// CREATE
case 'POST':
{
return [
// CREATE ROLES
];
}
// UPDATE
case 'PUT':
case 'PATCH':
{
return [
// UPDATE ROLES
];
}
case 'GET':
case 'DELETE':
default:
{
return [];
};
}
}
public function messages()
{
return [
// Validation messages
];
}
}
十七. 數(shù)據(jù)填充
1. factory 輔助函數(shù)
必須
使用 factory
方法來做數(shù)據(jù)填充,因?yàn)槭强蚣芴岢闹姹耄⑶铱梢酝瑫r(shí)為測(cè)試代碼服務(wù)矩动。
2. 運(yùn)行效率
開發(fā)數(shù)據(jù)填充時(shí),必須
特別注意 php artisan db:seed
的運(yùn)行效率释漆,否則隨著項(xiàng)目的代碼量越來越大悲没,db:seed
的運(yùn)行時(shí)間會(huì)變得越來越長(zhǎng),有些項(xiàng)目多達(dá)幾分鐘甚至幾十分鐘男图。
原則是:
Keep it lighting speed.
只有當(dāng) db:seed
運(yùn)行起來很快的時(shí)候示姿,才能完全利用數(shù)據(jù)填充工具帶來的便利,而不是累贅逊笆。
4. 批量入庫(kù)
所有假數(shù)據(jù)入庫(kù)操作栈戳,都 必須 是批量操作,配合 factory
使用以下方法:
$users = factory(User::class)->times(1000)->make();
User::insert($users->toArray());
以上只執(zhí)行一條數(shù)據(jù)庫(kù)語句难裆,推薦閱讀 大批量假數(shù)據(jù)填充的正確方法子檀。
十八. Artisan 命令行
所有的自定義命令,都 必須 有項(xiàng)目的命名空間。
如:
php artisan phphub:clear-token
php artisan phphub:send-status-email
...
錯(cuò)誤的例子為:
php artisan clear-token
php artisan send-status-email
...
十九. 日期和時(shí)間
必須 使用 Carbon 來處理日期和時(shí)間相關(guān)的操作命锄。
Laravel 5.1 中文的 diffForHumans
可以使用 jenssegers/date堰乔。
Laravel 5.3 及以上版本的 diffForHumans
,只需要在 config/app.php
文件中配置 locale
選項(xiàng)即可 :
'locale' => 'zh-CN',
二十. 前端開發(fā)
根據(jù) 分享下團(tuán)隊(duì)的開發(fā)規(guī)范 ——《Laravel 項(xiàng)目開發(fā)規(guī)范》脐恩,規(guī)范里這么寫的:
- 必須 使用 Laravel 官方前端工具做前端開發(fā)自動(dòng)化镐侯;
-
必須 保證頁(yè)面只加載一個(gè)
.css
文件; -
必須 保證頁(yè)面只加載一個(gè)
.js
文件驶冒; -
必須 為
.css
和.js
增加 版本控制苟翻; - 必須 使用 SASS 來書寫 CSS 代碼;
但是考慮到目前團(tuán)隊(duì)的情況骗污,在不前后端分離的情況下可以不執(zhí)行以上規(guī)范崇猫。
如果實(shí)行前后端分離,則前端必須使用vue腳手架需忿,并生成靜態(tài)文件诅炉,增加版本控制。
二十一. Laravel 安全實(shí)踐
1. 說明
沒有絕對(duì)安全屋厘,只有相對(duì)安全涕烧。Laravel 相較于其他框架在安全方面已經(jīng)做得很優(yōu)秀,不過作為開發(fā)者汗洒,我們要在日常開發(fā)中對(duì)『安全』需懷著敬畏之心议纯,積極培養(yǎng)自己的安全意識(shí)。以下是一些 Laravel 安全相關(guān)的規(guī)范溢谤。
2. 關(guān)閉 DEBUG
Laravel Debug 開啟時(shí)瞻凤,會(huì)暴露很多能被黑客利用的服務(wù)器信息,所以世杀,生產(chǎn)環(huán)境下請(qǐng) 必須 確保:
APP_DEBUG=false
3. XSS
跨站腳本攻擊(cross-site scripting阀参,簡(jiǎn)稱 XSS),具體危害體現(xiàn)在黑客能控制你網(wǎng)站頁(yè)面瞻坝,包括使用 JS 盜取 Cookie 等结笨,關(guān)于 XSS 的介紹請(qǐng)前往 IBM 文檔庫(kù):跨站點(diǎn)腳本攻擊深入解析 。
默認(rèn)情況下湿镀,在無法保證用戶提交內(nèi)容是 100% 安全的情況下,必須 使用 Blade 模板引擎的 {{ $content }}
語法會(huì)對(duì)用戶內(nèi)容進(jìn)行轉(zhuǎn)義伐憾。
Blade 的 {!! $content !!}
語法會(huì)直接對(duì)內(nèi)容進(jìn)行 非轉(zhuǎn)義 輸出勉痴,使用此語法時(shí),必須 使用 HTMLPurifier for Laravel 5 來為用戶輸入內(nèi)容進(jìn)行過濾树肃。使用方法參見: 使用 HTMLPurifier 來解決 Laravel 5 中的 XSS 跨站腳本攻擊安全問題
4. SQL 注入
Laravel 的 查詢構(gòu)造器 和 Eloquent 是基于 PHP 的 PDO蒸矛,PDO 使用 prepared
來準(zhǔn)備查詢語句,保障了安全性。
在使用 raw()
來編寫復(fù)雜查詢語句時(shí)雏掠,必須 使用數(shù)據(jù)綁定斩祭。
錯(cuò)誤的做法:
Route::get('sql-injection', function() {
$name = "admin"; // 假設(shè)用戶提交
$password = "xx' OR 1='1"; // // 假設(shè)用戶提交
$result = DB::select(DB::raw("SELECT * FROM users WHERE name ='$name' and password = '$password'"));
dd($result);
});
以下是正確的做法,利用 select 方法 的第二個(gè)參數(shù)做數(shù)據(jù)綁定:
Route::get('sql-injection', function() {
$name = "admin"; // 假設(shè)用戶提交
$password = "xx' OR 1='1"; // // 假設(shè)用戶提交
$result = DB::select(
DB::raw("SELECT * FROM users WHERE name =:name and password = :password"),
[
'name' => $name,
'password' => $password,
]
);
dd($result);
});
DB
類里的大部分執(zhí)行 SQL 的函數(shù)都可傳參第二個(gè)參數(shù) $bindings
乡话,詳見:API 文檔 摧玫。
(注:建議最好直接使用Laravel的Eloquent ORM來對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作)
5. 批量賦值
Laravel 提供白名單和黑名單過濾($fillable
和 $guarded
),開發(fā)者 應(yīng)該 清楚認(rèn)識(shí)批量賦值安全威脅的情況下合理靈活地運(yùn)用绑青。
批量賦值安全威脅诬像,指的是用戶可更新本來不應(yīng)有權(quán)限更新的字段。舉例闸婴,users
表里的 is_admin
字段是用來標(biāo)識(shí)用戶『是否是管理員』坏挠,某不懷好意的用戶,更改了『修改個(gè)人資料』的表單邪乍,增加了一個(gè)字段:
<input name="is_admin" value="1" />
這個(gè)時(shí)候如果你更新代碼如下:
Auth::user()->update(Request::all());
此用戶將獲取到管理員權(quán)限降狠。可以有很多種方法來避免這種情況出現(xiàn)庇楞,最簡(jiǎn)單的方法是通過設(shè)置 User 模型里的 $guarded
字段來避免:
protected $guarded = ['id', 'is_admin'];
6. CSRF
CSRF 跨站請(qǐng)求偽造是 Web 應(yīng)用中最常見的安全威脅之一榜配,具體請(qǐng)見 Wiki - 跨站請(qǐng)求偽造 或者 Web 應(yīng)用程序常見漏洞 CSRF 的入侵檢測(cè)與防范。
Laravel 默認(rèn)對(duì)所有『非冪等的請(qǐng)求』強(qiáng)制使用 VerifyCsrfToken
中間件防護(hù)姐刁,需要開發(fā)者做的芥牌,是區(qū)分清楚什么時(shí)候該使用『非冪等的請(qǐng)求』。
冪等請(qǐng)求指的是:'HEAD', 'GET', 'OPTIONS'聂使,既無論你執(zhí)行多少次重復(fù)的操作都不會(huì)給資源造成變更壁拉。
- 所有刪除的動(dòng)作,必須 使用 DELETE 作為請(qǐng)求方法柏靶;
- 所有對(duì)數(shù)據(jù)更新的動(dòng)作弃理,必須 使用 POST、PUT 或者 PATCH 請(qǐng)求方法屎蜓。
Laravel 自動(dòng)為每一個(gè)被應(yīng)用管理的有效用戶會(huì)話生成一個(gè) CSRF “令牌”痘昌,該令牌用于驗(yàn)證授權(quán)用戶和發(fā)起請(qǐng)求者是否是同一個(gè)人。想要生成包含 CSRF 令牌的隱藏輸入字段炬转,可以使用幫助函數(shù) csrf_field 來實(shí)現(xiàn):
<?php echo csrf_field(); ?>
輔助函數(shù) csrf_field 會(huì)生成如下 HTML:
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
當(dāng)然還可以使用 Blade 模板引擎提供的方式:
{!! csrf_field() !!}
你不需要自己編寫代碼去驗(yàn)證 POST辆苔、PUT 或者 DELETE 請(qǐng)求的 CSRF 令牌,因?yàn)?Laravel 自帶的 HTTP 中間件 VerifyCsrfToken 會(huì)為我們做這項(xiàng)工作:將請(qǐng)求中輸入的 token 值和 Session 中的存儲(chǔ)的 token 作對(duì)比來進(jìn)行驗(yàn)證扼劈。
X-CSRF-Token
除了將 CSRF 令牌作為 POST 參數(shù)進(jìn)行驗(yàn)證外驻啤,還可以通過設(shè)置 X-CSRF-Token 請(qǐng)求頭來實(shí)現(xiàn)驗(yàn)證,VerifyCsrfToken 中間件會(huì)檢查 X-CSRF-TOKEN 請(qǐng)求頭荐吵,首先創(chuàng)建一個(gè) meta 標(biāo)簽并將令牌保存到該 meta 標(biāo)簽:
<meta name="csrf-token" content="{{ csrf_token() }}">
然后在 js 庫(kù)(如 jQuery)中添加該令牌到所有請(qǐng)求頭骑冗,這為基于 AJAX 的應(yīng)用提供了簡(jiǎn)單赊瞬、方便的方式來避免 CSRF 攻擊:
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
二十三. Laravel 程序優(yōu)化
1. 配置信息緩存
生產(chǎn)環(huán)境中的 應(yīng)該 使用『配置信息緩存』來加速 Laravel 配置信息的讀取。
使用以下 Artisan 自帶命令贼涩,把 config
文件夾里所有配置信息合并到一個(gè)文件里巧涧,減少運(yùn)行時(shí)文件的載入數(shù)量:
php artisan config:cache
緩存文件存放在 bootstrap/cache/
文件夾中。
可以使用以下命令來取消配置信息緩存:
php artisan config:clear
注意:配置信息緩存不會(huì)隨著更新而自動(dòng)重載遥倦,所以谤绳,開發(fā)時(shí)候建議關(guān)閉配置信息緩存,一般在生產(chǎn)環(huán)境中使用谊迄∶乒可以配合 Envoy 任務(wù)運(yùn)行器 使用,在每次上線代碼時(shí)執(zhí)行 config:clear
命令统诺。
2. 路由緩存
生產(chǎn)環(huán)境中的 應(yīng)該 使用『路由緩存』來加速 Laravel 的路由注冊(cè)歪脏。
路由緩存可以有效的提高路由器的注冊(cè)效率,在大型應(yīng)用程序中效果越加明顯粮呢,可以使用以下命令:
php artisan route:cache
緩存文件存放在 bootstrap/cache/
文件夾中婿失。另外,路由緩存不支持路由匿名函數(shù)編寫邏輯啄寡,詳見:文檔 - 路由緩存豪硅。
可以使用下面命令清除路由緩存:
php artisan route:clear
注意:路由緩存不會(huì)隨著更新而自動(dòng)重載,所以挺物,開發(fā)時(shí)候建議關(guān)閉路由緩存懒浮,一般在生產(chǎn)環(huán)境中使用∈短伲可以配合 Envoy 任務(wù)運(yùn)行器 使用砚著,在每次上線代碼時(shí)執(zhí)行 route:clear
命令。
3. 類映射加載優(yōu)化
optimize
命令把常用加載的類合并到一個(gè)文件里痴昧,通過減少文件的加載稽穆,來提高運(yùn)行效率。生產(chǎn)環(huán)境中的 應(yīng)該使用 optimize 命令來優(yōu)化類的加載速度:
php artisan optimize --force
以上命令會(huì)在 bootstrap/cache/
文件夾中生成緩存文件赶撰。你可以可以通過修改 config/compile.php
文件來添加要合并的類舌镶。在 production
環(huán)境中,參數(shù) --force
不需要指定豪娜,文件就會(huì)自動(dòng)生成餐胀。
要清除類映射加載優(yōu)化,請(qǐng)運(yùn)行以下命令:
php artisan clear-compiled
此命令會(huì)刪除上面 optimize
生成的兩個(gè)文件瘤载。
注意:此命令要運(yùn)行在 php artisan config:cache
后骂澄,因?yàn)?optimize
命令是根據(jù)配置信息(如:config/app.php
文件的 providers
數(shù)組)來生成文件的。
4. 自動(dòng)加載優(yōu)化
此命令不止針對(duì)于 Laravel 程序惕虑,適用于所有使用 composer
來構(gòu)建的程序坟冲。此命令會(huì)把 PSR-0
和 PSR-4
轉(zhuǎn)換為一個(gè)類映射表,來提高類的加載速度溃蔫。
composer dumpautoload -o
注意:
php artisan optimize --force
命令里已經(jīng)做了這個(gè)操作健提。
5. 使用 Memcached 來存儲(chǔ)會(huì)話
每一個(gè) Laravel 的請(qǐng)求,都會(huì)產(chǎn)生會(huì)話伟叛,修改會(huì)話的存儲(chǔ)方式能有效提高程序效率私痹。會(huì)話的配置文件是 config/session.php
。生產(chǎn)環(huán)境中的 必須 使用 Memcached 或者 Redis 等專業(yè)的緩存軟件來存儲(chǔ)會(huì)話统刮,應(yīng)該 優(yōu)先選擇 Memcached(注:為了服務(wù)器方便管理紊遵,也可以用redis):
'driver' => 'memcached',
6. 使用專業(yè)緩存驅(qū)動(dòng)器
「緩存」是提高應(yīng)用程序運(yùn)行效率的法寶之一,Laravel 默認(rèn)緩存驅(qū)動(dòng)是 file
文件緩存侥蒙,生產(chǎn)環(huán)境中的 必須 使用專業(yè)的緩存系統(tǒng)暗膜,如 Redis 或者 Memcached。應(yīng)該 優(yōu)先考慮 Redis鞭衩。應(yīng)該 避免使用數(shù)據(jù)庫(kù)緩存学搜。
'default' => 'redis',
7. 數(shù)據(jù)庫(kù)請(qǐng)求優(yōu)化
關(guān)聯(lián)模型數(shù)據(jù)讀取時(shí) 必須 使用 延遲預(yù)加載 和 預(yù)加載 。
臨近上線時(shí) 必須 使用 Laravel Debugbar 或者 Clockwork 留意每一個(gè)頁(yè)面的總 SQL 請(qǐng)求條數(shù)论衍,進(jìn)行數(shù)據(jù)庫(kù)請(qǐng)求調(diào)優(yōu)瑞佩。
8. 為數(shù)據(jù)集書寫緩存邏輯
應(yīng)該 合理的使用 Laravel 提供的緩存層操作,把從數(shù)據(jù)庫(kù)里面拿出來的數(shù)據(jù)集合進(jìn)行緩存坯台,減少數(shù)據(jù)庫(kù)的壓力炬丸,運(yùn)行在內(nèi)存上的專業(yè)緩存軟件對(duì)數(shù)據(jù)的讀取也遠(yuǎn)遠(yuǎn)快于數(shù)據(jù)庫(kù)。
$hot_posts = Cache::remember('posts.hot_posts', $minutes = 30, function()
{
return Post::getHotPosts();
});
remember
甚至連數(shù)據(jù)關(guān)聯(lián)模型也都一并緩存了蜒蕾。
9. 使用即時(shí)編譯器
可以 使用 OpCache 進(jìn)行優(yōu)化稠炬。OpCache 都能輕輕松松的讓你的應(yīng)用程序在不用做任何修改的情況下,直接提高 50% 或者更高的性能滥搭,PHPhub 之前做個(gè)一個(gè)實(shí)驗(yàn)酸纲,具體請(qǐng)見:使用 OpCache 提升 PHP 5.5+ 程序性能。
二十四. 項(xiàng)目文檔編寫規(guī)范
1. 說明
每一個(gè)項(xiàng)目都 必須 包含一個(gè) readme.md
文件瑟匆,readme
里書寫這個(gè)項(xiàng)目的簡(jiǎn)單信息闽坡。作用主要有兩個(gè),一個(gè)是團(tuán)隊(duì)新成員可從此文件中快速獲悉項(xiàng)目大致情況愁溜,另一個(gè)是部署項(xiàng)目時(shí)可以作為參考疾嗅。
2. 排版規(guī)范
文檔頁(yè)面排版 必須 遵循 中文文案排版指北 ,在此基礎(chǔ)上:
- 中文文檔請(qǐng)使用全角標(biāo)點(diǎn)符號(hào)冕象;
- 必須 遵循 Markdown 語法代承,勿讓代碼顯示錯(cuò)亂;
- 原文中的雙引號(hào)(" ")請(qǐng)代換成中文的引號(hào)(『』符號(hào)怎么打出來見 這里)渐扮。
- 所有的 「
加亮
」论悴、「加粗」和「鏈接」都需要在左右保持一個(gè)空格掖棉。
3. 行文規(guī)范
readme.md
文檔 應(yīng)該 包含以下內(nèi)容:
- 「項(xiàng)目概述」- 介紹說明項(xiàng)目的一些情況,類似于簡(jiǎn)單的產(chǎn)品說明膀估,簡(jiǎn)單的功能描述幔亥,項(xiàng)目相關(guān)鏈接等,500 字以內(nèi)察纯;
- 「運(yùn)行環(huán)境」- 運(yùn)行環(huán)境說明帕棉,系統(tǒng)要求等信息;
- 「開發(fā)環(huán)境部署/安裝」- 一步一步引導(dǎo)說明饼记,保證項(xiàng)目新成員能最快速的香伴,沒有歧義的部署好開發(fā)環(huán)境;
- 「服務(wù)器架構(gòu)說明」- 最好能有服務(wù)器架構(gòu)圖具则,從用戶瀏覽器請(qǐng)求開始即纲,包括后端緩存服務(wù)使用等都描述清楚(主要體現(xiàn)為軟件的使用),配合「運(yùn)行環(huán)境」區(qū)塊內(nèi)容乡洼,可作為線上環(huán)境部署的依據(jù)崇裁;
- 「代碼上線」- 介紹代碼上線流程,需要執(zhí)行哪些步驟束昵;
- 「擴(kuò)展包說明」- 表格列出所有使用的擴(kuò)展包拔稳,還有在哪些業(yè)務(wù)邏輯或者用例中使用了此擴(kuò)展包;
- 「自定義 Artisan 命令列表」- 以表格形式羅列出所有自定義的命令锹雏,說明用途巴比,指出調(diào)用場(chǎng)景;
- 「隊(duì)列列表」- 以表格形式羅列出項(xiàng)目所有隊(duì)列接口礁遵,說明用途轻绞,指出調(diào)用場(chǎng)景。
一些額外補(bǔ)充
1. 一個(gè)方法做一件事情
一個(gè)方法做一件事情佣耐,盡量為每一塊邏輯用一個(gè)方法寫起來政勃,不要把全部邏輯放在同一個(gè)方法,不然很難維護(hù)兼砖,別人閱讀起來也很吃力奸远。
- 錯(cuò)誤的例子:
PostsController.php
<?php
namespace App\Controllers;
use App\Http\Controllers\Controller;
class PostsController extend Controller {
public function doSomething (Request $request) {
//檢查數(shù)據(jù)
//...
//查詢文章是否存在
$article = Article::find($request->input('id'));
if (!$article) {
//...
}
//添加文章記錄
//...
//添加用戶文章發(fā)布日志記錄
//...
}
}
- 正確的例子:
PostsController.php
<?php
namespace App\Controllers;
use App\Http\Controllers\Controller;
use App\Services\PostsService;
class PostsController extend Controller {
private $postsService;
public function __construct (PostsService $posts) {
$this->postsService = $posts;
}
/**
* 添加文章
*/
public function doSomething (Request $request) {
try {
$this->postsService->addArticle($request->input('user_id'), $request->input('data'));
return response()->json([...]);
} catch (\Exception $e) {
//捕捉拋出的異常處理
//...
}
}
}
PostsService.php
<?php
namespace App\Services;
class PostsService {
/**
* 添加文章
*/
public function addArticle ($userId, $data) {
//檢查數(shù)據(jù)
$check = $this->checkForAddArticle($userId, $data);
//添加文章記錄
$article = $this->doAddArticle($userId, $data);
//添加用戶文章發(fā)布日志記錄
$log = $this->addUserPublishLog($userId, $article);
//...
}
/**
* 檢查數(shù)據(jù)
*/
private function checkForAddArticle ($userId, $data) {
//檢查失敗,拋出異常讽挟,由控制器catch
//...
}
/**
* 添加文章記錄
*/
private function doAddArticle($userId, $data) {
//...
}
/**
* 添加用戶文章發(fā)布日志記錄
*/
private function addUserPublishLog($userId, $article) {
//...
}
}
2. 盡量要多做注釋說明
盡量多做注釋懒叛,這樣別人也能看得懂,自己需要維護(hù)后者修復(fù)bug的時(shí)候耽梅,也比較好找
3. 將常用數(shù)值寫入新建的配置文件中
一般數(shù)據(jù)庫(kù)會(huì)有一些狀態(tài)位薛窥,如 orders
表有 order_status
字段用來記錄訂單狀態(tài)
`order_status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '訂單狀態(tài): 0未付款 1已支付 2待配送 3派送中 4座位使用中 5已完成 6已取消 7超時(shí)未付款 8待退款 9已退款'
那么可以將這些值寫入一個(gè)新建的配置文件,如 config/params.php
中
<?php
return [
'order_status' => [
1 => 'unpaid',
2 => 'paid',
3 => 'wait_for_delivery',
...
9 => 'refunded'
],
//other config
];
然后通過用 config()
函數(shù)讀取
4. 一個(gè)請(qǐng)求一個(gè)方法
不要用一個(gè)方法執(zhí)行兩種類型的請(qǐng)求眼姐,get和post分別用不同的方法诅迷,不要通過如下去寫
if (!empty($_POST)) {}
5. 數(shù)據(jù)操作
不要直接執(zhí)行原生sql佩番,使用laravel的creat、insert竟贯、update答捕、save等方法,防止sql注入并且可以過濾掉非法數(shù)據(jù)屑那,最好使用Laravel的Eloquent ORM。不要使用以下寫法:
\DB::select("SELECT * FROM users WHERE id = 1");