Laravel 項(xiàng)目開發(fā)規(guī)范

一. 說明

以下內(nèi)容大部分引用Laravel China社區(qū)的文章 - 分享下團(tuán)隊(duì)的開發(fā)規(guī)范 ——《Laravel 項(xiàng)目開發(fā)規(guī)范》
相對而言,上面引用的文章的規(guī)范更加嚴(yán)格,但考慮到目前的情況,會適當(dāng)?shù)貙σ恍┮?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)格和思路,代碼閱讀起來如出一轍;
  • 減少錯誤 - 減小初級工程師的犯錯幾率。

四. 開發(fā)哲學(xué)

  • DRY –「Don't Repeat Yourself」不寫重復(fù)的邏輯代碼;
  • 約定俗成 - 「Convention Over Configuration」,優(yōu)先選擇框架提倡的做法,不過度配置;
  • KISS - 「Keep it Simple, Stupid」提倡簡單易讀的代碼,不寫高深、晦澀難懂的代碼,不過度設(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 動詞來組織你的程序;

在此規(guī)范中,我們會將使用這兩套理念作為程序設(shè)計(jì)基礎(chǔ)。這些設(shè)計(jì)理念為我們設(shè)計(jì)程序提供了依據(jù),遵循這些理念,能讓程序變得清晰易讀。

六. 能愿動詞

為了避免歧義,文檔大量使用了「能愿動詞」,對應(yīng)的解釋如下:

  • 必須(Must) - 只能這樣子做,請無條件遵循,沒有別的選項(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)定不升級到PHP7,那么 可以 考慮使用上一個(gè)版本的發(fā)行版。
比如Laravel 5.5是最新的LTS但是只支持PHP7以上,那么 可以 考慮使用Laravel 5.4。當(dāng)然比較可以使用Laravel 5.1 LTS版本,但是該版本比較舊,沒有新版本的一些新特性。

請使用以下命令來創(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 - 線上測試環(huán)境,對應(yīng)git的test分支
  • Production - 線上生產(chǎn)環(huán)境,對應(yīng)git的master分支

九. git分支

在創(chuàng)建git倉庫后,建議最好分開三個(gè)分支

  • 主分支 - master,對應(yīng)開發(fā)環(huán)境
  • 測試分支 - test,對應(yīng)線上測試環(huán)境
  • 開發(fā)分支 - develop,對應(yīng)線上生產(chǎn)環(huán)境

所有功能都是從develop分支新建分支,按功能模塊命名

  • 新的功能模塊,使用 features/功能名稱 來命名

  • 修復(fù)bug,使用 fix/bug名稱 來命名

  • 功能開發(fā)后,合并到develop

  • 開發(fā)測試通過后,將develop合并到test分支

  • 測試環(huán)境功能測試通過后,將test合并到master

十. 配置信息與環(huán)境變量

在 Laravel 中有以下幾種方法:

  1. 硬代碼,直接寫死。- ? 可維護(hù)性低
  2. 寫死在 config/app.php 文件中。 - ? 無法區(qū)分環(huán)境進(jìn)行配置
  3. 存儲于 .env 文件中,使用 env() 方法直接讀取。 - ? 雖然解決了環(huán)境變量問題但是不推薦
  4. 存儲在 .envconfig/app.php 文件中,然后使用 config() 函數(shù)來讀取。- ? 最佳實(shí)踐

第一種方法是最古老的方法,代碼可維護(hù)性極低,一旦域名變更就只能全局替換。
第二種方法無法區(qū)分環(huán)境,例如本地使用開發(fā)環(huán)境域名測試,線上才是正式的域名。
第三種方法雖然解決了環(huán)境變量的問題,并且也具備一定的靈活性,但是不夠靈活,假如你的網(wǎng)站流量巨大,需要配置幾個(gè)域名,使其在加載靜態(tài)資源時(shí)隨機(jī)支配域名,這種做法就無法滿足需求了。
第四種方法既支持環(huán)境變量,又具備極高的靈活性,假如遇到同樣的多域名隨機(jī)問題,你只需要寫一個(gè)輔助方法,然后在 config/app.php 中調(diào)用即可,不需要動到任何一行業(yè)務(wù)邏輯代碼。

代碼示例

.env 文件中設(shè)置:

DOMAIN=018eighteen.test

config/app.php 文件中設(shè)置:

'domain' => env('DOMAIN', '018eighteen.com'),

程序中兩種獲取 相同配置 的方法:

  1. env('DOMAIN')
  2. config('app.domain')

在此統(tǒng)一規(guī)定:所有程序配置信息 必須 通過 config() 來讀取,所有的 .env 配置信息 必須 通過 config() 來讀取,絕不 在配置文件以外的范圍使用 env()

這樣做主要有以下幾個(gè)優(yōu)勢:

  1. 定義分明,config() 是配置信息,env() 只是用來區(qū)分不同環(huán)境;
  2. 統(tǒng)一放置于 config 中還可以利用框架的 配置信息緩存功能 來提高運(yùn)行效率;
  3. 代碼健壯性, config()env() 之上多出來一個(gè)抽象層,會使代碼更加健壯,更加靈活。

十一. 路由器

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)于白名單,相對于 except 更加直觀。路由使用白名單有利于養(yǎng)成『安全習(xí)慣』。

4. 單數(shù) or 復(fù)數(shù)?

資源路由路由 URI 必須 使用復(fù)數(shù)形式,如:

  • /photos/create
  • /photos/{photo}

錯誤的例子如:

  • /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)先級:

  1. $model->link()
  2. route 方法
  3. 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'])

『單個(gè)模型 URI』經(jīng)常會發(fā)生變化,這樣做將會讓程序更加靈活。

除了『單個(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ù)庫表名字 必須 為「復(fù)數(shù)」,多個(gè)單詞情況下使用「Snake Case」 如:photos, my_photos (注:目前由于和其他團(tuán)隊(duì)合作開發(fā),所以這一條規(guī)范暫時(shí)不硬性要求)
  • 數(shù)據(jù)庫表遷移名字 必須 為「復(fù)數(shù)」,如:2014_08_08_234417_create_photos_table.php
  • 數(shù)據(jù)填充文件名 必須 為「復(fù)數(shù)」,如:PhotosTableSeeder.php
  • 數(shù)據(jù)庫字段名 必須 為「Snake Case」,如:view_count, is_vip
  • 數(shù)據(jù)庫表主鍵 必須 為「id」(注:這條規(guī)范一定要嚴(yán)格執(zhí)行,避免像018server的prouct表一樣出現(xiàn)product_id這樣的主鍵)
  • 數(shù)據(jù)庫表外鍵 必須 為「resource_id」,如:user_id, post_id
  • 數(shù)據(jù)模型變量 必須 為「resource_id」,如:$user_id, $post_id

4. 利用 Trait 來擴(kuò)展數(shù)據(jù)模型

有時(shí)候數(shù)據(jù)模型里的代碼會變得很臃腫,應(yīng)該 利用 Trait 來精簡邏輯代碼量,提高可讀性,類似于 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 會使得控制器越來越臃腫,有些代碼也會不停地重復(fù)寫,另外以后有可能需要編寫單元測試,所以最后還是決定啟用 Repository

具體參照為什么你應(yīng)該使用 Repository

6. 關(guān)于 SQL 文件

  • 絕不 使用命令行或者 PHPMyAdmin 直接創(chuàng)建索引或表。必須 使用 數(shù)據(jù)庫遷移 去創(chuàng)建表結(jié)構(gòu),并提交版本控制器中;
  • 絕不 為了共享對數(shù)據(jù)庫更改就直接導(dǎo)出 SQL,所有修改都 必須 使用 數(shù)據(jù)庫遷移 ,并提交版本控制器中;
  • 絕不 直接向數(shù)據(jù)庫手動寫入偽造的測試數(shù)據(jù)。必須 使用 數(shù)據(jù)填充 來插入假數(shù)據(jù),并提交版本控制器中。

考慮到可能會和其他團(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

錯誤的例子:

  • 類名:PhotoController
  • 文件名:PhotoController.php

3. 保持短小精煉

必須 保持控制器文件代碼行數(shù)最小化,還有可讀性。

  • 不應(yīng)該 為「方法」書寫注釋,這要求方法取名要足夠合理,不需要過多注釋;
  • 應(yīng)該 為一些復(fù)雜的邏輯代碼塊書寫注釋,主要介紹產(chǎn)品邏輯 - 為什么要這么做。
  • 不應(yīng)該 在控制器中書寫「私有方法」,控制器里 應(yīng)該 只存放「路由動作方法」;
  • 絕不 遺留「死方法」,就是沒有用到的方法,控制器里的所有方法,都應(yīng)該被使用到,否則應(yīng)該刪除;
  • 絕不 在控制器里批量注釋掉代碼,無用的邏輯代碼就必須清除掉。
  • 應(yīng)該 創(chuàng)建Service層建立對應(yīng)的Service類,以實(shí)現(xiàn)控制器對應(yīng)的邏輯,見下放關(guān)于Service

十四. Service

在上面提過決定啟用 RepositoryRepository主要是實(shí)現(xiàn)對model的增刪改查。
Service 則是介于 ControllerRepository 之間,是對 Controller 業(yè)務(wù)邏輯的實(shí)現(xiàn),實(shí)現(xiàn)過程中,通過 Repository 來對數(shù)據(jù)進(jìn)行操作。

1. 創(chuàng)建 Service 文件夾

首先我們需要在 app 文件夾創(chuàng)建自己 Service 文件夾 services,然后文件夾的每一個(gè)文件都要設(shè)置相應(yīng)的命名空間。

2. 創(chuàng)建對應(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)行前后端分離的情況下,請使用視圖

1. 優(yōu)先使用 Blade

視圖文件 必須 優(yōu)先考慮使用 .blade.php 后綴來指定使用 Blade 模板引擎。

2. 保持目錄清晰

  • layouts - 頁面布局文件 必須 放置于此目錄下;
  • common - 存放頁面通用元素;
  • pages - 簡單的頁面存放文件夾,如:about、contact 等;
  • resources - 對應(yīng) Restful 路由的資源路徑名稱,以 URI photos/create 為例,對應(yīng) create.blade.php 文件,存放在文件夾 photos 下。

必須 避免在 resources/views 目錄下直接放置視圖文件。

3. 局部視圖

局部視圖文件 必須 使用 _ 前綴來命名,如:photos/_upload_form.blade.php

4. 視圖命名要釋義

為了和 Restful 路由器和資源控制器保持一致,視圖命名也 必須 使用資源視圖的命名方式。以 photos 為例:

  • photos/index.blade.php
    • 內(nèi)容列表視圖
    • 對應(yīng)路由器 /photos,命名 photos.index
    • 控制器方法 PhotosController@index
  • photos/show.blade.php
    • 單個(gè)內(nèi)容視圖
    • 對應(yīng)路由器 /photos/{id},命名 photos.show
    • 控制器方法 PhotosController@show
  • photos/create.blade.php
    • 內(nèi)容創(chuàng)建視圖
    • 對應(yīng)路由器 /photos/create,命名 photos.create
    • 控制器方法 PhotosController@create
  • photos/edit.blade.php
    • 內(nèi)容編輯的視圖
    • 對應(yīng)路由器 /photos/edit,命名 photos.edit
    • 控制器方法 PhotosController@edit

5. create_and_edit 視圖

很多情況下,創(chuàng)建和編輯視圖里的頁面結(jié)構(gòu)接近相似,在這種情況下,應(yīng)該 使用 create_and_edit 視圖。以 photos 為例:

  • PhotosController@create - 對應(yīng)視圖:/photos/create_and_edit.blade.php
  • PhotosController@edit - 對應(yīng) 視圖:/photos/create_and_edit.blade.php

這樣一來,通常情況下,一個(gè)完整的 photos 資源對應(yīng)的視圖文件為以下:

├── photos
│   ├── create_and_edit.blade.php
│   ├── index.blade.php
│   └── show.blade.php

十六. 表單驗(yàn)證

1. 表單請求驗(yàn)證類

必須 使用 表單請求 - 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 對應(yīng) app/Http/Requests/PhotoRequest.php

4. 類文件參考

FormRequest 表驗(yàn)證類文件請參考以下:

<?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í)為測試代碼服務(wù)。

2. 運(yùn)行效率

開發(fā)數(shù)據(jù)填充時(shí),必須 特別注意 php artisan db:seed 的運(yùn)行效率,否則隨著項(xiàng)目的代碼量越來越大,db:seed 的運(yùn)行時(shí)間會變得越來越長,有些項(xiàng)目多達(dá)幾分鐘甚至幾十分鐘。

原則是:

Keep it lighting speed.

只有當(dāng) db:seed 運(yùn)行起來很快的時(shí)候,才能完全利用數(shù)據(jù)填充工具帶來的便利,而不是累贅。

4. 批量入庫

所有假數(shù)據(jù)入庫操作,都 必須 是批量操作,配合 factory 使用以下方法:

$users = factory(User::class)->times(1000)->make();
User::insert($users->toArray());

以上只執(zhí)行一條數(shù)據(jù)庫語句,推薦閱讀 大批量假數(shù)據(jù)填充的正確方法

十八. Artisan 命令行

所有的自定義命令,都 必須 有項(xiàng)目的命名空間。

如:

php artisan phphub:clear-token
php artisan phphub:send-status-email
...

錯誤的例子為:

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ā)自動化;
  • 必須 保證頁面只加載一個(gè) .css 文件;
  • 必須 保證頁面只加載一個(gè) .js 文件;
  • 必須.css.js 增加 版本控制
  • 必須 使用 SASS 來書寫 CSS 代碼;

但是考慮到目前團(tuán)隊(duì)的情況,在不前后端分離的情況下可以不執(zhí)行以上規(guī)范。
如果實(shí)行前后端分離,則前端必須使用vue腳手架,并生成靜態(tài)文件,增加版本控制。

二十一. Laravel 安全實(shí)踐

1. 說明

沒有絕對安全,只有相對安全。Laravel 相較于其他框架在安全方面已經(jīng)做得很優(yōu)秀,不過作為開發(fā)者,我們要在日常開發(fā)中對『安全』需懷著敬畏之心,積極培養(yǎng)自己的安全意識。以下是一些 Laravel 安全相關(guān)的規(guī)范。

2. 關(guān)閉 DEBUG

Laravel Debug 開啟時(shí),會暴露很多能被黑客利用的服務(wù)器信息,所以,生產(chǎn)環(huán)境下請 必須 確保:

APP_DEBUG=false

3. XSS

跨站腳本攻擊(cross-site scripting,簡稱 XSS),具體危害體現(xiàn)在黑客能控制你網(wǎng)站頁面,包括使用 JS 盜取 Cookie 等,關(guān)于 XSS 的介紹請前往 IBM 文檔庫:跨站點(diǎn)腳本攻擊深入解析

默認(rèn)情況下,在無法保證用戶提交內(nèi)容是 100% 安全的情況下,必須 使用 Blade 模板引擎的 {{ $content }} 語法會對用戶內(nèi)容進(jìn)行轉(zhuǎn)義。

Blade 的 {!! $content !!} 語法會直接對內(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ù)綁定。

錯誤的做法:

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來對數(shù)據(jù)庫進(jìn)行操作)

5. 批量賦值

Laravel 提供白名單和黑名單過濾($fillable$guarded),開發(fā)者 應(yīng)該 清楚認(rèn)識批量賦值安全威脅的情況下合理靈活地運(yùn)用。

批量賦值安全威脅,指的是用戶可更新本來不應(yīng)有權(quán)限更新的字段。舉例,users 表里的 is_admin 字段是用來標(biāo)識用戶『是否是管理員』,某不懷好意的用戶,更改了『修改個(gè)人資料』的表單,增加了一個(gè)字段:

<input name="is_admin" value="1" />

這個(gè)時(shí)候如果你更新代碼如下:

Auth::user()->update(Request::all());

此用戶將獲取到管理員權(quán)限。可以有很多種方法來避免這種情況出現(xiàn),最簡單的方法是通過設(shè)置 User 模型里的 $guarded 字段來避免:

protected $guarded = ['id', 'is_admin'];

6. CSRF

CSRF 跨站請求偽造是 Web 應(yīng)用中最常見的安全威脅之一,具體請見 Wiki - 跨站請求偽造 或者 Web 應(yīng)用程序常見漏洞 CSRF 的入侵檢測與防范

Laravel 默認(rèn)對所有『非冪等的請求』強(qiáng)制使用 VerifyCsrfToken 中間件防護(hù),需要開發(fā)者做的,是區(qū)分清楚什么時(shí)候該使用『非冪等的請求』。

冪等請求指的是:'HEAD', 'GET', 'OPTIONS',既無論你執(zhí)行多少次重復(fù)的操作都不會給資源造成變更。

  • 所有刪除的動作,必須 使用 DELETE 作為請求方法;
  • 所有對數(shù)據(jù)更新的動作,必須 使用 POST、PUT 或者 PATCH 請求方法。

Laravel 自動為每一個(gè)被應(yīng)用管理的有效用戶會話生成一個(gè) CSRF “令牌”,該令牌用于驗(yàn)證授權(quán)用戶和發(fā)起請求者是否是同一個(gè)人。想要生成包含 CSRF 令牌的隱藏輸入字段,可以使用幫助函數(shù) csrf_field 來實(shí)現(xiàn):

<?php echo csrf_field(); ?>

輔助函數(shù) csrf_field 會生成如下 HTML:

<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">

當(dāng)然還可以使用 Blade 模板引擎提供的方式:

{!! csrf_field() !!}

你不需要自己編寫代碼去驗(yàn)證 POST、PUT 或者 DELETE 請求的 CSRF 令牌,因?yàn)?Laravel 自帶的 HTTP 中間件 VerifyCsrfToken 會為我們做這項(xiàng)工作:將請求中輸入的 token 值和 Session 中的存儲的 token 作對比來進(jìn)行驗(yàn)證。

X-CSRF-Token
除了將 CSRF 令牌作為 POST 參數(shù)進(jìn)行驗(yàn)證外,還可以通過設(shè)置 X-CSRF-Token 請求頭來實(shí)現(xiàn)驗(yàn)證,VerifyCsrfToken 中間件會檢查 X-CSRF-TOKEN 請求頭,首先創(chuàng)建一個(gè) meta 標(biāo)簽并將令牌保存到該 meta 標(biāo)簽:

<meta name="csrf-token" content="{{ csrf_token() }}">

然后在 js 庫(如 jQuery)中添加該令牌到所有請求頭,這為基于 AJAX 的應(yīng)用提供了簡單、方便的方式來避免 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

注意:配置信息緩存不會隨著更新而自動重載,所以,開發(fā)時(shí)候建議關(guān)閉配置信息緩存,一般在生產(chǎn)環(huán)境中使用。可以配合 Envoy 任務(wù)運(yùn)行器 使用,在每次上線代碼時(shí)執(zhí)行 config:clear 命令。

2. 路由緩存

生產(chǎn)環(huán)境中的 應(yīng)該 使用『路由緩存』來加速 Laravel 的路由注冊。

路由緩存可以有效的提高路由器的注冊效率,在大型應(yīng)用程序中效果越加明顯,可以使用以下命令:

php artisan route:cache

緩存文件存放在 bootstrap/cache/ 文件夾中。另外,路由緩存不支持路由匿名函數(shù)編寫邏輯,詳見:文檔 - 路由緩存

可以使用下面命令清除路由緩存:

php artisan route:clear

注意:路由緩存不會隨著更新而自動重載,所以,開發(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

以上命令會在 bootstrap/cache/ 文件夾中生成緩存文件。你可以可以通過修改 config/compile.php 文件來添加要合并的類。在 production 環(huán)境中,參數(shù) --force 不需要指定,文件就會自動生成。

要清除類映射加載優(yōu)化,請運(yùn)行以下命令:

php artisan clear-compiled

此命令會刪除上面 optimize 生成的兩個(gè)文件。

注意:此命令要運(yùn)行在 php artisan config:cache 后,因?yàn)?optimize 命令是根據(jù)配置信息(如:config/app.php 文件的 providers 數(shù)組)來生成文件的。

4. 自動加載優(yōu)化

此命令不止針對于 Laravel 程序,適用于所有使用 composer 來構(gòu)建的程序。此命令會把 PSR-0PSR-4轉(zhuǎn)換為一個(gè)類映射表,來提高類的加載速度。

composer dumpautoload -o

注意:php artisan optimize --force 命令里已經(jīng)做了這個(gè)操作。

5. 使用 Memcached 來存儲會話

每一個(gè) Laravel 的請求,都會產(chǎn)生會話,修改會話的存儲方式能有效提高程序效率。會話的配置文件是 config/session.php。生產(chǎn)環(huán)境中的 必須 使用 Memcached 或者 Redis 等專業(yè)的緩存軟件來存儲會話,應(yīng)該 優(yōu)先選擇 Memcached(注:為了服務(wù)器方便管理,也可以用redis):

'driver' => 'memcached',

6. 使用專業(yè)緩存驅(qū)動器

「緩存」是提高應(yīng)用程序運(yùn)行效率的法寶之一,Laravel 默認(rèn)緩存驅(qū)動是 file 文件緩存,生產(chǎn)環(huán)境中的 必須 使用專業(yè)的緩存系統(tǒng),如 Redis 或者 Memcached。應(yīng)該 優(yōu)先考慮 Redis。應(yīng)該 避免使用數(shù)據(jù)庫緩存。

'default' => 'redis',

7. 數(shù)據(jù)庫請求優(yōu)化

關(guān)聯(lián)模型數(shù)據(jù)讀取時(shí) 必須 使用 延遲預(yù)加載預(yù)加載

臨近上線時(shí) 必須 使用 Laravel Debugbar 或者 Clockwork 留意每一個(gè)頁面的總 SQL 請求條數(shù),進(jìn)行數(shù)據(jù)庫請求調(diào)優(yōu)。

8. 為數(shù)據(jù)集書寫緩存邏輯

應(yīng)該 合理的使用 Laravel 提供的緩存層操作,把從數(shù)據(jù)庫里面拿出來的數(shù)據(jù)集合進(jìn)行緩存,減少數(shù)據(jù)庫的壓力,運(yùn)行在內(nèi)存上的專業(yè)緩存軟件對數(shù)據(jù)的讀取也遠(yuǎn)遠(yuǎn)快于數(shù)據(jù)庫。

$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),具體請見:使用 OpCache 提升 PHP 5.5+ 程序性能

二十四. 項(xiàng)目文檔編寫規(guī)范

1. 說明

每一個(gè)項(xiàng)目都 必須 包含一個(gè) readme.md 文件,readme 里書寫這個(gè)項(xiàng)目的簡單信息。作用主要有兩個(gè),一個(gè)是團(tuán)隊(duì)新成員可從此文件中快速獲悉項(xiàng)目大致情況,另一個(gè)是部署項(xiàng)目時(shí)可以作為參考。

2. 排版規(guī)范

文檔頁面排版 必須 遵循 中文文案排版指北 ,在此基礎(chǔ)上:

  • 中文文檔請使用全角標(biāo)點(diǎn)符號;
  • 必須 遵循 Markdown 語法,勿讓代碼顯示錯亂;
  • 原文中的雙引號(" ")請代換成中文的引號(『』符號怎么打出來見 這里)。
  • 所有的 「加亮」、「加粗」和「鏈接」都需要在左右保持一個(gè)空格。

3. 行文規(guī)范

readme.md 文檔 應(yīng)該 包含以下內(nèi)容:

  • 「項(xiàng)目概述」- 介紹說明項(xiàng)目的一些情況,類似于簡單的產(chǎ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)圖,從用戶瀏覽器請求開始,包括后端緩存服務(wù)使用等都描述清楚(主要體現(xiàn)為軟件的使用),配合「運(yùn)行環(huán)境」區(qū)塊內(nèi)容,可作為線上環(huán)境部署的依據(jù);
  • 「代碼上線」- 介紹代碼上線流程,需要執(zhí)行哪些步驟;
  • 「擴(kuò)展包說明」- 表格列出所有使用的擴(kuò)展包,還有在哪些業(yè)務(wù)邏輯或者用例中使用了此擴(kuò)展包;
  • 「自定義 Artisan 命令列表」- 以表格形式羅列出所有自定義的命令,說明用途,指出調(diào)用場景;
  • 「隊(duì)列列表」- 以表格形式羅列出項(xiàng)目所有隊(duì)列接口,說明用途,指出調(diào)用場景。

范例見 附錄:readme-example.md

一些額外補(bǔ)充

1. 一個(gè)方法做一件事情

一個(gè)方法做一件事情,盡量為每一塊邏輯用一個(gè)方法寫起來,不要把全部邏輯放在同一個(gè)方法,不然很難維護(hù),別人閱讀起來也很吃力。

  • 錯誤的例子:
    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ù)庫會有一些狀態(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è)請求一個(gè)方法

不要用一個(gè)方法執(zhí)行兩種類型的請求,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");
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 原文鏈接 必備品 文檔:Documentation API:API Reference 視頻:Laracasts ...
    layjoy閱讀 8,629評論 0 121
  • 必備品文檔:DocumentationAPI: API Reference視頻:Laracasts速查表:Lara...
    ethanzhang閱讀 5,781評論 0 68
  • 2015年的夏天 我從半年前就開始倒數(shù)這個(gè)夏天我在等 你和我說好的 再見面的約定我在等 我等了那么久的一句回答 以...
    DREAMER追夢人閱讀 212評論 0 1
  • 北宋詩人蘇軾 的《念奴嬌·赤壁懷古》寫道: 大江東去,浪淘盡,千古風(fēng)流人物。 故壘西邊,人道是,三國周郎赤壁。亂石...
    如歌的行板紫雪閱讀 991評論 1 3
  • 這些年你很辛苦,但是你很堅(jiān)強(qiáng),你是我的母親,也是我的驕傲。 這些年你受的辛苦和難處,我也都記得。 我也很愛你。 未...
    以諾爸爸閱讀 165評論 0 0