一. 說明
以下內容大部分引用Laravel China社區的文章 - 分享下團隊的開發規範 ——《Laravel 項目開發規範》。
相對而言,上面引用的文章的規範更加嚴格,但考慮到目前的情況,會適當地對一些規範進行更改和增刪。
二. 目的
暫無
三. 優點
規範有一下優點:
- 高效編碼 - 避免了過多的選擇造成的『決策時間』浪費;
- 風格統一 - 最大程度統一了開發團隊成員代碼書寫風格和思路,代碼閱讀起來如出一轍;
- 減少錯誤 - 減小初級工程師的犯錯機率。
四. 開發哲學
- DRY –「Don't Repeat Yourself」不寫重複的邏輯代碼;
- 約定俗成 - 「Convention Over Configuration」,優先選擇框架提倡的做法,不過度配置;
- KISS - 「Keep it Simple, Stupid」提倡簡單易讀的代碼,不寫高深、晦澀難懂的代碼,不過度設計;
- 主廚精選 - 讓有經驗的人來爲你選擇方案,不獨創方案;
- 官方提倡 - 優先選擇官方推崇的方案。
五. 設計理念
以下是一些優秀的『程序設計理念』:
- MVC - Model, View, Controller ,以 MVC 爲核心,嚴格控制 Controller 的可讀性和代碼行數;
- Restful - 利用『資源化概念』和標準的 HTTP 動詞來組織你的程序;
在此規範中,我們會將使用這兩套理念作爲程序設計基礎。這些設計理念爲我們設計程序提供了依據,遵循這些理念,能讓程序變得清晰易讀。
六. 能願動詞
爲了避免歧義,文檔大量使用了「能願動詞」,對應的解釋如下:
- 必須(Must) - 只能這樣子做,請無條件遵循,沒有別的選項;
- 絕不(Must Not)- 嚴令禁止,在任何情況下都不能這樣做;
- 應該(Should) - 強烈建議這樣做,但是不強求;
- 不應該(Should Not) - 強烈建議不這樣做,但是不強求;
- 可以(May) - 選擇性高一點,在這個文檔內,此詞語使用較少;
七. 關於Laravel版本的選擇
選擇Laravel版本時,應該 優先選擇LTS版本,除非有特殊原因,如生產服務器的PHP版本不是PHP7以上,而是PHP5.*,且爲了穩定不升級到PHP7,那麼 可以 考慮使用上一個版本的發行版。
比如Laravel 5.5是最新的LTS但是隻支持PHP7以上,那麼 可以 考慮使用Laravel 5.4。當然比較可以使用Laravel 5.1 LTS版本,但是該版本比較舊,沒有新版本的一些新特性。
請使用以下命令來創建指定版本的 Laravel 項目:
composer create-project laravel/laravel project-name --prefer-dist "5.5.*"
絕不 也禁止使用複製粘貼項目文件的方式來創建項目。
八. 環境說明
一般情況下,一個項目 應該 有以下三個基本的項目環境:
- Local - 開發環境
- Staging - 線上測試環境,對應git的test分支
- Production - 線上生產環境,對應git的master分支
九. git分支
在創建git倉庫後,建議最好分開三個分支
- 主分支 - master,對應開發環境
- 測試分支 - test,對應線上測試環境
- 開發分支 - develop,對應線上生產環境
所有功能都是從develop分支新建分支,按功能模塊命名
-
新的功能模塊,使用
features/功能名稱
來命名 -
修復bug,使用
fix/bug名稱
來命名 -
功能開發後,合併到develop
-
開發測試通過後,將develop合併到test分支
-
測試環境功能測試通過後,將test合併到master
十. 配置信息與環境變量
在 Laravel 中有以下幾種方法:
- 硬代碼,直接寫死。- ❌ 可維護性低
- 寫死在
config/app.php
文件中。 - ❌ 無法區分環境進行配置 - 存儲於
.env
文件中,使用env()
方法直接讀取。 - ❌ 雖然解決了環境變量問題但是不推薦 - 存儲在
.env
和config/app.php
文件中,然後使用config()
函數來讀取。- ✅ 最佳實踐
第一種方法是最古老的方法,代碼可維護性極低,一旦域名變更就只能全局替換。
第二種方法無法區分環境,例如本地使用開發環境域名測試,線上纔是正式的域名。
第三種方法雖然解決了環境變量的問題,並且也具備一定的靈活性,但是不夠靈活,假如你的網站流量巨大,需要配置幾個域名,使其在加載靜態資源時隨機支配域名,這種做法就無法滿足需求了。
第四種方法既支持環境變量,又具備極高的靈活性,假如遇到同樣的多域名隨機問題,你只需要寫一個輔助方法,然後在 config/app.php
中調用即可,不需要動到任何一行業務邏輯代碼。
代碼示例
.env
文件中設置:
DOMAIN=018eighteen.test
config/app.php
文件中設置:
'domain' => env('DOMAIN', '018eighteen.com'),
程序中兩種獲取 相同配置 的方法:
env('DOMAIN')
config('app.domain')
在此統一規定:所有程序配置信息 必須 通過 config()
來讀取,所有的 .env
配置信息 必須 通過 config()
來讀取,絕不 在配置文件以外的範圍使用 env()
。
這樣做主要有以下幾個優勢:
- 定義分明,
config()
是配置信息,env()
只是用來區分不同環境; - 統一放置於
config
中還可以利用框架的 配置信息緩存功能 來提高運行效率; - 代碼健壯性,
config()
在env()
之上多出來一個抽象層,會使代碼更加健壯,更加靈活。
十一. 路由器
1. 路由閉包
絕不 在路由配置文件裏書寫『閉包路由』或者其他業務邏輯代碼,因爲一旦使用將無法使用 路由緩存 。
路由器要保持乾淨整潔,絕不 放置除路由配置以外的其他程序邏輯。
2. Restful 路由
必須 優先使用 Restful 路由,配合資源控制器使用,見 文檔。
超出 Restful 路由的,應該 模仿上圖的方式來定義路由。
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
方法時,如果僅使用到部分路由,必須 使用 only
列出所有可用路由:
Route::resource('photos', 'PhotosController', ['only' => ['index', 'show']]);
絕不 使用 except
,因爲 only
相當於白名單,相對於 except
更加直觀。路由使用白名單有利於養成『安全習慣』。
4. 單數 or 複數?
資源路由路由 URI 必須 使用複數形式,如:
/photos/create
/photos/{photo}
錯誤的例子如:
/photo/create
/photo/{photo}
5. 路由命名
除了 resource
資源路由以外,其他所有路由都 必須 使用 name
方法進行命名。
必須 使用『資源前綴』作爲命名規範,如下的 users.follow
,資源前綴的值是 users.
:
Route::post('users/{id}/follow', 'UsersController@follow')->name('users.follow');
6. 獲取 URL
獲取 URL 必須 遵循以下優先級:
$model->link()
route
方法url
方法
在 Model 中創建 link()
方法:
public function link($params = [])
{
$params = array_merge([$this->id], $params);
return route('models.show', $params);
}
所有單個模型數據鏈接使用:
$model->link();
// 或者添加參數
$model->link($params = ['source' => 'list'])
『單個模型 URI』經常會發生變化,這樣做將會讓程序更加靈活。
除了『單個模型 URI』,其他路由 必須 使用 route
來獲取 URL(這也是目前使用次數最多的方法):
$url = route('profile', ['id' => 1]);
無法使用 route
的情況下,可以 使用 url
方法來獲取 URL:
url('profile', [1]);
十二. 數據模型
1. 放置位置
所有的數據模型文件,都 必須 存放在:app/Models/
文件夾中。
命名空間:
namespace App\Models;
2. 使用基類
所有的 Eloquent 數據模型 都 必須 繼承統一的基類 App/Models/Model
,此基類存放位置爲 /app/Models/Model.php
,內容參考以下:
<?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 數據模型作爲例子繼承 Model 基類:
<?php
namespace App\Models;
class Photo extends Model
{
protected $fillable = ['id', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
3. 命名規範[#]
數據模型相關的命名規範:
- 數據模型類名
必須
爲「單數」, 如:App\Models\Photo
- 類文件名
必須
爲「單數」,如:app/Models/Photo.php
- 數據庫表名字
必須
爲「複數」,多個單詞情況下使用「Snake Case」 如:photos
,my_photos
(注:目前由於和其他團隊合作開發,所以這一條規範暫時不硬性要求) - 數據庫表遷移名字
必須
爲「複數」,如:2014_08_08_234417_create_photos_table.php
- 數據填充文件名
必須
爲「複數」,如:PhotosTableSeeder.php
- 數據庫字段名
必須
爲「Snake Case」,如:view_count
,is_vip
- 數據庫表主鍵
必須
爲「id」(注:這條規範一定要嚴格執行,避免像018server的prouct
表一樣出現product_id
這樣的主鍵) - 數據庫表外鍵
必須
爲「resource_id」,如:user_id
,post_id
- 數據模型變量
必須
爲「resource_id」,如:$user_id
,$post_id
4. 利用 Trait 來擴展數據模型
有時候數據模型裏的代碼會變得很臃腫,應該 利用 Trait 來精簡邏輯代碼量,提高可讀性,類似於 Ruby China 源碼。
借鑑於 Rails 的設計理念:「Fat Models, Skinny Controllers」。
存放於文件夾:app/Models/Traits
文件夾中。
5. Repository
在分享下團隊的開發規範 ——《Laravel 項目開發規範》 提出不適用 Repository
模式進行開發,但是考慮到隨着功能越來越多,不適用 Repository
會使得控制器越來越臃腫,有些代碼也會不停地重複寫,另外以後有可能需要編寫單元測試,所以最後還是決定啓用 Repository
。
6. 關於 SQL 文件
- 絕不 使用命令行或者 PHPMyAdmin 直接創建索引或表。必須 使用 數據庫遷移 去創建表結構,並提交版本控制器中;
- 絕不 爲了共享對數據庫更改就直接導出 SQL,所有修改都 必須 使用 數據庫遷移 ,並提交版本控制器中;
- 絕不 直接向數據庫手動寫入僞造的測試數據。必須 使用 數據填充 來插入假數據,並提交版本控制器中。
考慮到可能會和其他團隊合作開發,所以具體還是根據團隊的協定而定。但是如果是自己團隊開發的話,必須嚴格按照以上標準。
十三. 控制器
1. 資源控制器
必須 優先使用 Restful 資源控制器 。
2. 單數 or 複數?
必須 使用資源的複數形式,如:
- 類名:PhotosController
- 文件名:PhotosController.php
錯誤的例子:
- 類名:PhotoController
- 文件名:PhotoController.php
3. 保持短小精煉
必須 保持控制器文件代碼行數最小化,還有可讀性。
- 不應該 爲「方法」書寫註釋,這要求方法取名要足夠合理,不需要過多註釋;
- 應該 爲一些複雜的邏輯代碼塊書寫註釋,主要介紹產品邏輯 -
爲什麼要這麼做。
; - 不應該 在控制器中書寫「私有方法」,控制器裏
應該
只存放「路由動作方法」; - 絕不 遺留「死方法」,就是沒有用到的方法,控制器裏的所有方法,都應該被使用到,否則應該刪除;
- 絕不 在控制器裏批量註釋掉代碼,無用的邏輯代碼就必須清除掉。
- 應該 創建Service層建立對應的Service類,以實現控制器對應的邏輯,見下放關於Service
十四. Service
在上面提過決定啓用 Repository
,Repository
主要是實現對model的增刪改查。
而 Service
則是介於 Controller
與 Repository
之間,是對 Controller
業務邏輯的實現,實現過程中,通過 Repository
來對數據進行操作。
1. 創建 Service
文件夾
首先我們需要在 app
文件夾創建自己 Service
文件夾 services
,然後文件夾的每一個文件都要設置相應的命名空間。
2. 創建對應的 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...
}
}
十五. 視圖
在不進行前後端分離的情況下,請使用視圖
1. 優先使用 Blade
視圖文件 必須 優先考慮使用 .blade.php
後綴來指定使用 Blade 模板引擎。
2. 保持目錄清晰
- layouts - 頁面佈局文件 必須 放置於此目錄下;
- common - 存放頁面通用元素;
- pages - 簡單的頁面存放文件夾,如:about、contact 等;
- resources - 對應 Restful 路由的資源路徑名稱,以 URI
photos/create
爲例,對應create.blade.php
文件,存放在文件夾photos
下。
必須 避免在 resources/views
目錄下直接放置視圖文件。
3. 局部視圖
局部視圖文件 必須 使用 _
前綴來命名,如:photos/_upload_form.blade.php
4. 視圖命名要釋義
爲了和 Restful 路由器和資源控制器保持一致,視圖命名也 必須 使用資源視圖的命名方式。以 photos
爲例:
photos/index.blade.php
- 內容列表視圖
- 對應路由器
/photos
,命名photos.index
- 控制器方法
PhotosController@index
photos/show.blade.php
- 單個內容視圖
- 對應路由器
/photos/{id}
,命名photos.show
- 控制器方法
PhotosController@show
photos/create.blade.php
- 內容創建視圖
- 對應路由器
/photos/create
,命名photos.create
- 控制器方法
PhotosController@create
photos/edit.blade.php
- 內容編輯的視圖
- 對應路由器
/photos/edit
,命名photos.edit
- 控制器方法
PhotosController@edit
5. create_and_edit
視圖
很多情況下,創建和編輯視圖裏的頁面結構接近相似,在這種情況下,應該 使用 create_and_edit
視圖。以 photos
爲例:
PhotosController@create
- 對應視圖:/photos/create_and_edit.blade.php
PhotosController@edit
- 對應 視圖:/photos/create_and_edit.blade.php
這樣一來,通常情況下,一個完整的 photos
資源對應的視圖文件爲以下:
├── photos
│ ├── create_and_edit.blade.php
│ ├── index.blade.php
│ └── show.blade.php
十六. 表單驗證
1. 表單請求驗證類
必須 使用 表單請求 - FormRequest 類 來處理控制器裏的表單驗證。
2. 使用基類
所有 FormRequest 表驗證類 必須 繼承 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. 驗證類命名
FormRequest 表驗證類 必須 遵循 資源路由 方式進行命名,photos
對應 app/Http/Requests/PhotoRequest.php
。
4. 類文件參考
FormRequest 表驗證類文件請參考以下:
<?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
];
}
}
十七. 數據填充
1. factory 輔助函數
必須
使用 factory
方法來做數據填充,因爲是框架提倡的,並且可以同時爲測試代碼服務。
2. 運行效率
開發數據填充時,必須
特別注意 php artisan db:seed
的運行效率,否則隨着項目的代碼量越來越大,db:seed
的運行時間會變得越來越長,有些項目多達幾分鐘甚至幾十分鐘。
原則是:
Keep it lighting speed.
只有當 db:seed
運行起來很快的時候,才能完全利用數據填充工具帶來的便利,而不是累贅。
4. 批量入庫
所有假數據入庫操作,都 必須 是批量操作,配合 factory
使用以下方法:
$users = factory(User::class)->times(1000)->make();
User::insert($users->toArray());
以上只執行一條數據庫語句,推薦閱讀 大批量假數據填充的正確方法。
十八. Artisan 命令行
所有的自定義命令,都 必須 有項目的命名空間。
如:
php artisan phphub:clear-token
php artisan phphub:send-status-email
...
錯誤的例子爲:
php artisan clear-token
php artisan send-status-email
...
十九. 日期和時間
必須 使用 Carbon 來處理日期和時間相關的操作。
Laravel 5.1 中文的 diffForHumans
可以使用 jenssegers/date。
Laravel 5.3 及以上版本的 diffForHumans
,只需要在 config/app.php
文件中配置 locale
選項即可 :
'locale' => 'zh-CN',
二十. 前端開發
根據 分享下團隊的開發規範 ——《Laravel 項目開發規範》,規範裏這麼寫的:
- 必須 使用 Laravel 官方前端工具做前端開發自動化;
- 必須 保證頁面只加載一個
.css
文件; - 必須 保證頁面只加載一個
.js
文件; - 必須 爲
.css
和.js
增加 版本控制; - 必須 使用 SASS 來書寫 CSS 代碼;
但是考慮到目前團隊的情況,在不前後端分離的情況下可以不執行以上規範。
如果實行前後端分離,則前端必須使用vue腳手架,並生成靜態文件,增加版本控制。
二十一. Laravel 安全實踐
1. 說明
沒有絕對安全,只有相對安全。Laravel 相較於其他框架在安全方面已經做得很優秀,不過作爲開發者,我們要在日常開發中對『安全』需懷着敬畏之心,積極培養自己的安全意識。以下是一些 Laravel 安全相關的規範。
2. 關閉 DEBUG
Laravel Debug 開啓時,會暴露很多能被黑客利用的服務器信息,所以,生產環境下請 必須 確保:
APP_DEBUG=false
3. XSS
跨站腳本攻擊(cross-site scripting,簡稱 XSS),具體危害體現在黑客能控制你網站頁面,包括使用 JS 盜取 Cookie 等,關於 XSS 的介紹請前往 IBM 文檔庫:跨站點腳本攻擊深入解析 。
默認情況下,在無法保證用戶提交內容是 100% 安全的情況下,必須 使用 Blade 模板引擎的 {{ $content }}
語法會對用戶內容進行轉義。
Blade 的 {!! $content !!}
語法會直接對內容進行 非轉義 輸出,使用此語法時,必須 使用 HTMLPurifier for Laravel 5 來爲用戶輸入內容進行過濾。使用方法參見: 使用 HTMLPurifier 來解決 Laravel 5 中的 XSS 跨站腳本攻擊安全問題
4. SQL 注入
Laravel 的 查詢構造器 和 Eloquent 是基於 PHP 的 PDO,PDO 使用 prepared
來準備查詢語句,保障了安全性。
在使用 raw()
來編寫複雜查詢語句時,必須 使用數據綁定。
錯誤的做法:
Route::get('sql-injection', function() {
$name = "admin"; // 假設用戶提交
$password = "xx' OR 1='1"; // // 假設用戶提交
$result = DB::select(DB::raw("SELECT * FROM users WHERE name ='$name' and password = '$password'"));
dd($result);
});
以下是正確的做法,利用 select 方法 的第二個參數做數據綁定:
Route::get('sql-injection', function() {
$name = "admin"; // 假設用戶提交
$password = "xx' OR 1='1"; // // 假設用戶提交
$result = DB::select(
DB::raw("SELECT * FROM users WHERE name =:name and password = :password"),
[
'name' => $name,
'password' => $password,
]
);
dd($result);
});
DB
類裏的大部分執行 SQL 的函數都可傳參第二個參數 $bindings
,詳見:API 文檔 。
(注:建議最好直接使用Laravel的Eloquent ORM來對數據庫進行操作)
5. 批量賦值
Laravel 提供白名單和黑名單過濾($fillable
和 $guarded
),開發者 應該 清楚認識批量賦值安全威脅的情況下合理靈活地運用。
批量賦值安全威脅,指的是用戶可更新本來不應有權限更新的字段。舉例,users
表裏的 is_admin
字段是用來標識用戶『是否是管理員』,某不懷好意的用戶,更改了『修改個人資料』的表單,增加了一個字段:
<input name="is_admin" value="1" />
這個時候如果你更新代碼如下:
Auth::user()->update(Request::all());
此用戶將獲取到管理員權限。可以有很多種方法來避免這種情況出現,最簡單的方法是通過設置 User 模型裏的 $guarded
字段來避免:
protected $guarded = ['id', 'is_admin'];
6. CSRF
CSRF 跨站請求僞造是 Web 應用中最常見的安全威脅之一,具體請見 Wiki - 跨站請求僞造 或者 Web 應用程序常見漏洞 CSRF 的入侵檢測與防範。
Laravel 默認對所有『非冪等的請求』強制使用 VerifyCsrfToken
中間件防護,需要開發者做的,是區分清楚什麼時候該使用『非冪等的請求』。
冪等請求指的是:'HEAD', 'GET', 'OPTIONS',既無論你執行多少次重複的操作都不會給資源造成變更。
- 所有刪除的動作,必須 使用 DELETE 作爲請求方法;
- 所有對數據更新的動作,必須 使用 POST、PUT 或者 PATCH 請求方法。
Laravel 自動爲每一個被應用管理的有效用戶會話生成一個 CSRF “令牌”,該令牌用於驗證授權用戶和發起請求者是否是同一個人。想要生成包含 CSRF 令牌的隱藏輸入字段,可以使用幫助函數 csrf_field 來實現:
<?php echo csrf_field(); ?>
輔助函數 csrf_field 會生成如下 HTML:
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
當然還可以使用 Blade 模板引擎提供的方式:
{!! csrf_field() !!}
你不需要自己編寫代碼去驗證 POST、PUT 或者 DELETE 請求的 CSRF 令牌,因爲 Laravel 自帶的 HTTP 中間件 VerifyCsrfToken 會爲我們做這項工作:將請求中輸入的 token 值和 Session 中的存儲的 token 作對比來進行驗證。
X-CSRF-Token
除了將 CSRF 令牌作爲 POST 參數進行驗證外,還可以通過設置 X-CSRF-Token 請求頭來實現驗證,VerifyCsrfToken 中間件會檢查 X-CSRF-TOKEN 請求頭,首先創建一個 meta 標籤並將令牌保存到該 meta 標籤:
<meta name="csrf-token" content="{{ csrf_token() }}">
然後在 js 庫(如 jQuery)中添加該令牌到所有請求頭,這爲基於 AJAX 的應用提供了簡單、方便的方式來避免 CSRF 攻擊:
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
二十三. Laravel 程序優化
1. 配置信息緩存
生產環境中的 應該 使用『配置信息緩存』來加速 Laravel 配置信息的讀取。
使用以下 Artisan 自帶命令,把 config
文件夾裏所有配置信息合併到一個文件裏,減少運行時文件的載入數量:
php artisan config:cache
緩存文件存放在 bootstrap/cache/
文件夾中。
可以使用以下命令來取消配置信息緩存:
php artisan config:clear
注意:配置信息緩存不會隨着更新而自動重載,所以,開發時候建議關閉配置信息緩存,一般在生產環境中使用。可以配合 Envoy 任務運行器 使用,在每次上線代碼時執行 config:clear
命令。
2. 路由緩存
生產環境中的 應該 使用『路由緩存』來加速 Laravel 的路由註冊。
路由緩存可以有效的提高路由器的註冊效率,在大型應用程序中效果越加明顯,可以使用以下命令:
php artisan route:cache
緩存文件存放在 bootstrap/cache/
文件夾中。另外,路由緩存不支持路由匿名函數編寫邏輯,詳見:文檔 - 路由緩存。
可以使用下面命令清除路由緩存:
php artisan route:clear
注意:路由緩存不會隨着更新而自動重載,所以,開發時候建議關閉路由緩存,一般在生產環境中使用。可以配合 Envoy 任務運行器 使用,在每次上線代碼時執行 route:clear
命令。
3. 類映射加載優化
optimize
命令把常用加載的類合併到一個文件裏,通過減少文件的加載,來提高運行效率。生產環境中的 應該使用 optimize 命令來優化類的加載速度:
php artisan optimize --force
以上命令會在 bootstrap/cache/
文件夾中生成緩存文件。你可以可以通過修改 config/compile.php
文件來添加要合併的類。在 production
環境中,參數 --force
不需要指定,文件就會自動生成。
要清除類映射加載優化,請運行以下命令:
php artisan clear-compiled
此命令會刪除上面 optimize
生成的兩個文件。
注意:此命令要運行在 php artisan config:cache
後,因爲 optimize
命令是根據配置信息(如:config/app.php
文件的 providers
數組)來生成文件的。
4. 自動加載優化
此命令不止針對於 Laravel 程序,適用於所有使用 composer
來構建的程序。此命令會把 PSR-0
和 PSR-4
轉換爲一個類映射表,來提高類的加載速度。
composer dumpautoload -o
注意:
php artisan optimize --force
命令裏已經做了這個操作。
5. 使用 Memcached 來存儲會話
每一個 Laravel 的請求,都會產生會話,修改會話的存儲方式能有效提高程序效率。會話的配置文件是 config/session.php
。生產環境中的 必須 使用 Memcached 或者 Redis 等專業的緩存軟件來存儲會話,應該 優先選擇 Memcached(注:爲了服務器方便管理,也可以用redis):
'driver' => 'memcached',
6. 使用專業緩存驅動器
「緩存」是提高應用程序運行效率的法寶之一,Laravel 默認緩存驅動是 file
文件緩存,生產環境中的 必須 使用專業的緩存系統,如 Redis 或者 Memcached。應該 優先考慮 Redis。應該 避免使用數據庫緩存。
'default' => 'redis',
7. 數據庫請求優化
臨近上線時 必須 使用 Laravel Debugbar 或者 Clockwork 留意每一個頁面的總 SQL 請求條數,進行數據庫請求調優。
8. 爲數據集書寫緩存邏輯
應該 合理的使用 Laravel 提供的緩存層操作,把從數據庫裏面拿出來的數據集合進行緩存,減少數據庫的壓力,運行在內存上的專業緩存軟件對數據的讀取也遠遠快於數據庫。
$hot_posts = Cache::remember('posts.hot_posts', $minutes = 30, function()
{
return Post::getHotPosts();
});
remember
甚至連數據關聯模型也都一併緩存了。
9. 使用即時編譯器
可以 使用 OpCache 進行優化。OpCache 都能輕輕鬆鬆的讓你的應用程序在不用做任何修改的情況下,直接提高 50% 或者更高的性能,PHPhub 之前做個一個實驗,具體請見:使用 OpCache 提升 PHP 5.5+ 程序性能。
二十四. 項目文檔編寫規範
1. 說明
每一個項目都 必須 包含一個 readme.md
文件,readme
裏書寫這個項目的簡單信息。作用主要有兩個,一個是團隊新成員可從此文件中快速獲悉項目大致情況,另一個是部署項目時可以作爲參考。
2. 排版規範
文檔頁面排版 必須 遵循 中文文案排版指北 ,在此基礎上:
- 中文文檔請使用全角標點符號;
- 必須 遵循 Markdown 語法,勿讓代碼顯示錯亂;
- 原文中的雙引號(" ")請代換成中文的引號(『』符號怎麼打出來見 這裏)。
- 所有的 「
加亮
」、「加粗」和「鏈接」都需要在左右保持一個空格。
3. 行文規範
readme.md
文檔 應該 包含以下內容:
- 「項目概述」- 介紹說明項目的一些情況,類似於簡單的產品說明,簡單的功能描述,項目相關鏈接等,500 字以內;
- 「運行環境」- 運行環境說明,系統要求等信息;
- 「開發環境部署/安裝」- 一步一步引導說明,保證項目新成員能最快速的,沒有歧義的部署好開發環境;
- 「服務器架構說明」- 最好能有服務器架構圖,從用戶瀏覽器請求開始,包括後端緩存服務使用等都描述清楚(主要體現爲軟件的使用),配合「運行環境」區塊內容,可作爲線上環境部署的依據;
- 「代碼上線」- 介紹代碼上線流程,需要執行哪些步驟;
- 「擴展包說明」- 表格列出所有使用的擴展包,還有在哪些業務邏輯或者用例中使用了此擴展包;
- 「自定義 Artisan 命令列表」- 以表格形式羅列出所有自定義的命令,說明用途,指出調用場景;
- 「隊列列表」- 以表格形式羅列出項目所有隊列接口,說明用途,指出調用場景。
一些額外補充
1. 一個方法做一件事情
一個方法做一件事情,儘量爲每一塊邏輯用一個方法寫起來,不要把全部邏輯放在同一個方法,不然很難維護,別人閱讀起來也很喫力。
- 錯誤的例子:
PostsController.php
<?php
namespace App\Controllers;
use App\Http\Controllers\Controller;
class PostsController extend Controller {
public function doSomething (Request $request) {
//檢查數據
//...
//查詢文章是否存在
$article = Article::find($request->input('id'));
if (!$article) {
//...
}
//添加文章記錄
//...
//添加用戶文章發佈日誌記錄
//...
}
}
- 正確的例子:
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) {
//檢查數據
$check = $this->checkForAddArticle($userId, $data);
//添加文章記錄
$article = $this->doAddArticle($userId, $data);
//添加用戶文章發佈日誌記錄
$log = $this->addUserPublishLog($userId, $article);
//...
}
/**
* 檢查數據
*/
private function checkForAddArticle ($userId, $data) {
//檢查失敗,拋出異常,由控制器catch
//...
}
/**
* 添加文章記錄
*/
private function doAddArticle($userId, $data) {
//...
}
/**
* 添加用戶文章發佈日誌記錄
*/
private function addUserPublishLog($userId, $article) {
//...
}
}
2. 儘量要多做註釋說明
儘量多做註釋,這樣別人也能看得懂,自己需要維護後者修復bug的時候,也比較好找
3. 將常用數值寫入新建的配置文件中
一般數據庫會有一些狀態位,如 orders
表有 order_status
字段用來記錄訂單狀態
`order_status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '訂單狀態: 0未付款 1已支付 2待配送 3派送中 4座位使用中 5已完成 6已取消 7超時未付款 8待退款 9已退款'
那麼可以將這些值寫入一個新建的配置文件,如 config/params.php
中
<?php
return [
'order_status' => [
1 => 'unpaid',
2 => 'paid',
3 => 'wait_for_delivery',
...
9 => 'refunded'
],
//other config
];
然後通過用 config()
函數讀取
4. 一個請求一個方法
不要用一個方法執行兩種類型的請求,get和post分別用不同的方法,不要通過如下去寫
if (!empty($_POST)) {}
5. 數據操作
不要直接執行原生sql,使用laravel的creat、insert、update、save等方法,防止sql注入並且可以過濾掉非法數據,最好使用Laravel的Eloquent ORM。不要使用以下寫法:
\DB::select("SELECT * FROM users WHERE id = 1");
作者:knghlp508
鏈接:https://www.jianshu.com/p/e464a35e5ed2
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。