本文翻譯改編自 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 | |
Route | 復(fù)數(shù) | articles/1 | |
Named route | 帶點符號的蛇形命名 | users.show_active | |
Model | 單數(shù) | User | |
hasOne or belongsTo relationship | 單數(shù) | articleComment | |
All other relationships | 復(fù)數(shù) | articleComments | |
Table | 復(fù)數(shù) | article_comments | |
Pivot table | 按字母順序排列的單數(shù)模型名稱 | article_user | |
Table column | 帶著模型名稱的蛇形命名 | meta_title | |
Foreign key | 帶_id后綴的單數(shù)型號名稱 | article_id | |
Primary key | - | id | |
Migration | - | 2017_01_01_000000_create_articles_table | |
Method | 小駝峰命名 | getAll | |
Method in resource controller | 具體看表格 | store | |
Method in test class | 小駝峰命名 | testGuestCannotSeeArticle | |
Variable | 小駝峰命名 | $articlesWithAuthor | |
Collection | 具描述性的復(fù)數(shù)形式 | $activeUsers = User::active()->get() | |
Object | 具描述性的單數(shù)形式 | $activeUser = User::active()->first() | |
Config and language files index | 蛇形命名 | articles_enabled | |
View | 蛇形命名 | show_filtered.blade.php | |
Config | 蛇形命名 | google_calendar.php | |
Contract (interface) | 形容詞或名詞 | Authenticatable | |
Trait | 形容詞 | Notifiable |
盡可能使用更短、更易讀的語法
壞:
$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ū)