Laravel 的十八個最佳實踐

file

本文翻譯改編自 Laravel 的十八個最佳實踐

這篇文章并不是什么由 Laravel 改編的 SOLID 原則蜜笤、模式等越庇。

只是為了讓你注意你在現(xiàn)實生活的 Laravel 項目中最常忽略的內(nèi)容继控。

單一責(zé)任原則

一個類和一個方法應(yīng)該只有一個職責(zé)斟薇。
錯誤的做法:

public function getFullNameAttribute()
{
    if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
        return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' $this->last_name;
    } else {
        return $this->first_name[0] . '. ' . $this->last_name;
    }
}

推薦的做法:

public function getFullNameAttribute()
{
    return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}

public function isVerfiedClient()
{
    return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}

public function getFullNameLong()
{
    return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}

public function getFullNameShort()
{
    return $this->first_name[0] . '. ' . $this->last_name;
}

強(qiáng)大的模型 & 簡單控制器

如果你使用查詢構(gòu)造器或原始 SQL 來查詢,請將所有與數(shù)據(jù)庫相關(guān)的邏輯放入 Eloquent 模型或存儲庫類中耍攘。

壞:

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}

好:

public function index()
{
    return view('index', ['clients' => $this->client->getWithNewOrders()]);
}

Class Client extends Model
{
    public function getWithNewOrders()
    {
        return $this->verified()
            ->with(['orders' => function ($q) {
                $q->where('created_at', '>', Carbon::today()->subWeek());
            }])
            ->get();
    }
}

驗證

將驗證從控制器移動到請求類榕栏。

很常見但不推薦的做法:

public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);

    ....
}

最好是這樣:

public function store(PostRequest $request)
{    
    ....
}

class PostRequest extends Request
{
    public function rules()
    {
        return [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
            'publish_at' => 'nullable|date',
        ];
    }
}

業(yè)務(wù)邏輯應(yīng)該在服務(wù)類中

一個控制器必須只有一個職責(zé),因此應(yīng)該將業(yè)務(wù)邏輯從控制器移到服務(wù)類少漆。

壞:

public function store(Request $request)
{
    if ($request->hasFile('image')) {
        $request->file('image')->move(public_path('images') . 'temp');
    }
    
    ....
}

好:

public function store(Request $request)
{
    $this->articleService->handleUploadedImage($request->file('image'));

    ....
}

class ArticleService
{
    public function handleUploadedImage($image)
    {
        if (!is_null($image)) {
            $image->move(public_path('images') . 'temp');
        }
    }
}

不要重復(fù)你自己(DRY)

盡可能重用代碼臼膏。 SRP(單一職責(zé)原則)正在幫助你避免重復(fù)。當(dāng)然示损,這也包括了 Blade 模板渗磅、Eloquent 的范圍等。

壞:

public function getActive()
{
    return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}

public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->where('verified', 1)->whereNotNull('deleted_at');
        })->get();
}

好:

public function scopeActive($q)
{
    return $q->where('verified', 1)->whereNotNull('deleted_at');
}

public function getActive()
{
    return $this->active()->get();
}

public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->active();
        })->get();
}

最好傾向于使用 Eloquent 而不是 Query Builder 和原生的 SQL 查詢检访。要優(yōu)先于數(shù)組的集合

Eloquent 可以編寫可讀和可維護(hù)的代碼始鱼。此外,Eloquent 也擁有很棒的內(nèi)置工具脆贵,比如軟刪除医清、事件、范圍等卖氨。

比如你這樣寫:

SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
              FROM `users`
              WHERE `articles`.`user_id` = `users`.`id`
              AND EXISTS (SELECT *
                          FROM `profiles`
                          WHERE `profiles`.`user_id` = `users`.`id`) 
              AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC

還不如這樣寫:

Article::has('user.profile')->verified()->latest()->get();

批量賦值

比如你這樣寫:

$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Add category to article
$article->category_id = $category->id;
$article->save();

是不是還不如這樣寫:

$category->article()->create($request->all());

不要在 Blade 模板中執(zhí)行查詢并使用關(guān)聯(lián)加載(N + 1 問題)

不好的地方在于会烙,這對于100 個用戶來說,等于執(zhí)行 101 個 DB 查詢:

[@foreach](https://laravel-china.org/users/5651) (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

下面的做法筒捺,對于 100 個用戶來說柏腻,僅僅只執(zhí)行 2 個 DB 查詢:

$users = User::with('profile')->get();

...

[[@foreach](https://laravel-china.org/users/5651)](https://laravel-china.org/users/5651) ($users as $user)
    {{ $user->profile->name }}
@endforeach

與其花盡心思給你的代碼寫注釋,還不如對方法或變量寫一個描述性的名稱

壞:

if (count((array) $builder->getQuery()->joins) > 0)

好:

// 確定是否有任何連接系吭。
if (count((array) $builder->getQuery()->joins) > 0)

最好:

if ($this->hasJoins())

不要把 JS 和 CSS 放在 Blade 模板中五嫂,也不要將任何 HTML 放在 PHP 類中

壞:

let article = `{{ json_encode($article) }}`;

好:

<input id="article" type="hidden" value="{{ json_encode($article) }}">

Or

<button class="js-fav-article" data-article="{{ json_encode($article) }}">{{ $article->name }}<button>

最好的方法是使用在 Javascript 中這樣來傳輸數(shù)據(jù):

let article = $('#article').val();

在代碼中使用配置和語言文件、常量肯尺,而不是寫死它

壞:

public function isNormal()
{
    return $article->type === 'normal';
}

return back()->with('message', 'Your article has been added!');

好:

public function isNormal()
{
    return $article->type === Article::TYPE_NORMAL;
}

return back()->with('message', __('app.article_added'));

使用社區(qū)接受的標(biāo)準(zhǔn)的 Laravel 工具

最好使用內(nèi)置的 Laravel 功能和社區(qū)軟件包沃缘,而不是其他第三方軟件包和工具。因為將來與你的應(yīng)用程序一起工作的開發(fā)人員都需要學(xué)習(xí)新的工具则吟。另外槐臀,使用第三方軟件包或工具的話,如果遇到困難氓仲,從 Laravel 社區(qū)獲得幫助的機(jī)會會大大降低水慨。不要讓你的客戶為此付出代價败匹!

任務(wù) 標(biāo)準(zhǔn)工具 第三方工具
授權(quán) Policies Entrust, Sentinel and other packages
前端編譯 Laravel Mix Grunt, Gulp, 3rd party packages
開發(fā)環(huán)境 Homestead Docker
部署 Laravel Forge Deployer and other solutions
單元測試 PHPUnit, Mockery Phpspec
瀏覽器測試 Laravel Dusk Codeception
數(shù)據(jù)庫操作 Eloquent SQL, Doctrine
模板 Blade Twig
數(shù)據(jù)操作 Laravel collections Arrays
表單驗證 Request classes 3rd party packages, validation in controller
認(rèn)證 Built-in 3rd party packages, your own solution
API 認(rèn)證 Laravel Passport 3rd party JWT and OAuth packages
創(chuàng)建 API Built-in Dingo API and similar packages
數(shù)據(jù)庫結(jié)構(gòu)操作 Migrations Working with DB structure directly
局部化 Built-in 3rd party packages
實時用戶接口 Laravel Echo, Pusher 3rd party packages and working with WebSockets directly
Generating testing data Seeder classes, Model Factories, Faker Creating testing data manually
生成測試數(shù)據(jù) Laravel Task Scheduler Scripts and 3rd party packages
數(shù)據(jù)庫 MySQL, PostgreSQL, SQLite, SQL Server MongoDB

遵循Laravel命名約定

遵循 PSR 標(biāo)準(zhǔn)。 另外讥巡,請遵循 Laravel 社區(qū)接受的命名約定:

類型 規(guī)則 正確示例 錯誤示例
Controller 單數(shù) ArticleController ArticlesController
Route 復(fù)數(shù) articles/1 article/1
Named route 帶點符號的蛇形命名 users.show_active users.show-active, show-active-users
Model 單數(shù) User Users
hasOne or belongsTo relationship 單數(shù) articleComment articleComments, article_comment
All other relationships 復(fù)數(shù) articleComments articleComment, article_comments
Table 復(fù)數(shù) article_comments article_comment, articleComments
Pivot table 按字母順序排列的單數(shù)模型名稱 article_user user_article, articles_users
Table column 帶著模型名稱的蛇形命名 meta_title MetaTitle; article_meta_title
Foreign key 帶_id后綴的單數(shù)型號名稱 article_id ArticleId, id_article, articles_id
Primary key - id custom_id
Migration - 2017_01_01_000000_create_articles_table 2017_01_01_000000_articles
Method 小駝峰命名 getAll get_all
Method in resource controller 具體看表格 store saveArticle
Method in test class 小駝峰命名 testGuestCannotSeeArticle test_guest_cannot_see_article
Variable 小駝峰命名 $articlesWithAuthor $articles_with_author
Collection 具描述性的復(fù)數(shù)形式 $activeUsers = User::active()->get() $active, $data
Object 具描述性的單數(shù)形式 $activeUser = User::active()->first() $users, $obj
Config and language files index 蛇形命名 articles_enabled ArticlesEnabled; articles-enabled
View 蛇形命名 show_filtered.blade.php showFiltered.blade.php, show-filtered.blade.php
Config 蛇形命名 google_calendar.php googleCalendar.php, google-calendar.php
Contract (interface) 形容詞或名詞 Authenticatable AuthenticationInterface, IAuthentication
Trait 形容詞 Notifiable NotificationTrait

盡可能使用更短、更易讀的語法

壞:

$request->session()->get('cart');
$request->input('name');

好:

session('cart');
$request->name;

更多示例:

通用語法 更短舔哪、更可讀的語法
Session::get('cart') session('cart')
$request->session()->get('cart') session('cart')
Session::put('cart', $data) session(['cart' => $data])
$request->input('name'), Request::get('name') $request->name, request('name')
return Redirect::back() return back()
is_null($object->relation) ? $object->relation->id : null } optional($object->relation)->id
return view('index')->with('title', $title)->with('client', $client) return view('index', compact('title', 'client'))
$request->has('value') ? $request->value : 'default'; $request->get('value', 'default')
Carbon::now(), Carbon::today() now(), today()
App::make('Class') app('Class')
->where('column', '=', 1) ->where('column', 1)
->orderBy('created_at', 'desc') ->latest()
->orderBy('age', 'desc') ->latest('age')
->orderBy('created_at', 'asc') ->oldest()
->select('id', 'name')->get() ->get(['id', 'name'])
->first()->name ->value('name')

使用 IoC 容器或 facades 代替新的 Class

新的 Class 語法創(chuàng)建類時欢顷,不僅使得類與類之間緊密耦合,還加重了測試的復(fù)雜度捉蚤。推薦改用 IoC 容器或 facades抬驴。

壞:

$user = new User;
$user->create($request->all());

好:

public function __construct(User $user)
{
    $this->user = $user;
}

....

$this->user->create($request->all());

不要直接從 .env 文件獲取數(shù)據(jù)

將數(shù)據(jù)傳遞給配置文件,然后使用輔助函數(shù) config() 在應(yīng)用程序中使用數(shù)據(jù)缆巧。

壞:

$apiKey = env('API_KEY');

好:

// config/api.php
'key' => env('API_KEY'),

// Use the data
$apiKey = config('api.key');

以標(biāo)準(zhǔn)格式存儲日期布持,必要時就使用訪問器和修改器來修改日期格式

壞:

{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}

好:

// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at']
public function getMonthDayAttribute($date)
{
    return $date->format('m-d');
}

// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->monthDay }}

其他良好做法

  • 千萬不要在路由文件中放置任何邏輯。
  • 在 Blade 模板中最小化 vanilla PHP 的使用陕悬。

更多現(xiàn)代化 PHP 知識题暖,請前往 Laravel / PHP 知識社區(qū)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市捉超,隨后出現(xiàn)的幾起案子胧卤,更是在濱河造成了極大的恐慌,老刑警劉巖拼岳,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枝誊,死亡現(xiàn)場離奇詭異,居然都是意外死亡惜纸,警方通過查閱死者的電腦和手機(jī)叶撒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耐版,“玉大人祠够,你說我怎么就攤上這事⊥指” “怎么了哪审?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虑瀑。 經(jīng)常有香客問我湿滓,道長,這世上最難降的妖魔是什么舌狗? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任叽奥,我火速辦了婚禮,結(jié)果婚禮上痛侍,老公的妹妹穿的比我還像新娘朝氓。我一直安慰自己魔市,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布赵哲。 她就那樣靜靜地躺著待德,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枫夺。 梳的紋絲不亂的頭發(fā)上将宪,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音橡庞,去河邊找鬼较坛。 笑死,一個胖子當(dāng)著我的面吹牛扒最,可吹牛的內(nèi)容都是我干的丑勤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼吧趣,長吁一口氣:“原來是場噩夢啊……” “哼法竞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起再菊,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤爪喘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纠拔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秉剑,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年稠诲,在試婚紗的時候發(fā)現(xiàn)自己被綠了侦鹏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡臀叙,死狀恐怖略水,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劝萤,我是刑警寧澤渊涝,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站床嫌,受9級特大地震影響跨释,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厌处,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一鳖谈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧阔涉,春花似錦缆娃、人聲如沸捷绒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽暖侨。三九已至,卻和暖如春崇渗,著一層夾襖步出監(jiān)牢的瞬間它碎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工显押, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人傻挂。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓乘碑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親金拒。 傳聞我的和親對象是個殘疾皇子兽肤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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