Laravel 最佳實踐
單一職責原則
一個類和一個方法應該只有一個責任。
例如:
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 isVerifiedClient()
{
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;
}
複製程式碼
保持控制器的簡潔
如果您使用的是查詢生成器或原始SQL查詢,請將所有與資料庫相關的邏輯放入Eloquent模型或Repository類中。
例如:
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();
}
}
複製程式碼
使用自定義Request類來進行驗證
把驗證規則放到 Request 類中.
例子:
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',];
}
}
複製程式碼
業務程式碼要放到服務層中
控制器必須遵循單一職責原則,因此最好將業務程式碼從控制器移動到服務層中。
例子:
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');
}
}
}
複製程式碼
DRY原則 不要重複自己
儘可能重用程式碼,SRP可以幫助您避免重複造輪子。 此外儘量重複使用Blade模板,使用Eloquent的 scopes 方法來實現程式碼。
例子:
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();
}
複製程式碼
使用ORM而不是純sql語句,使用集合而不是陣列
使用Eloquent可以幫您編寫可讀和可維護的程式碼。 此外Eloquent還有非常優雅的內建工具,如軟刪除,事件,範圍等。
例子:
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->validated());
複製程式碼
不要在模板中查詢,儘量使用惰性載入
例子 (對於100個使用者,將執行101次DB查詢):
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
複製程式碼
更優的寫法 (對於100個使用者,使用以下寫法只需執行2次DB查詢):
$users = User::with('profile')->get();
...
@foreach ($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($article)">
Or
<button class="js-fav-article" data-article="@json($article)">{{ $article->name }}<button>
複製程式碼
在Javascript檔案中加上:
let article = $('#article').val();
複製程式碼
當然最好的辦法還是使用專業的PHP的JS包傳輸資料。
在程式碼中使用配置、語言包和常量,而不是使用硬編碼
例子:
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'));
複製程式碼
使用社群認可的標準Laravel工具
強力推薦使用內建的Laravel功能和擴充套件包,而不是使用第三方的擴充套件包和工具。 如果你的專案被其他開發人員接手了,他們將不得不重新學習這些第三方工具的使用教程。 此外,當您使用第三方擴充套件包或工具時,你很難從Laravel社群獲得什麼幫助。 不要讓你的客戶為額外的問題付錢。
想要實現的功能 | 標準工具 | 第三方工具 |
---|---|---|
許可權 | Policies | Entrust,Sentinel 或者其他擴充套件包 |
資源編譯工具 | Laravel Mix | Grunt,Gulp,或者其他第三方包 |
開發環境 | Homestead | Docker |
部署 | Laravel Forge | Deployer 或者其他解決方案 |
自動化測試 | PHPUnit,Mockery | Phpspec |
頁面預覽測試 | Laravel Dusk | Codeception |
DB操縱 | Eloquent | SQL,Doctrine |
模板 | Blade | Twig |
資料操縱 | Laravel集合 | 陣列 |
表單驗證 | Request classes | 他第三方包,甚至在控制器中做驗證 |
許可權 | Built-in | 他第三方包或者你自己解決 |
API身份驗證 | Laravel Passport | 第三方的JWT或者 OAuth 擴充套件包 |
建立 API | Built-in | Dingo API 或者類似的擴充套件包 |
建立資料庫結構 | Migrations | 直接用 DB 語句建立 |
本土化 | Built-in | 第三方包 |
實時訊息佇列 | Laravel Echo,Pusher | 使用第三方包或者直接使用WebSockets |
建立測試資料 | Seeder classes,Model Factories,Faker | 手動建立測試資料 |
任務排程 | Laravel Task Scheduler | 指令碼和第三方包 |
資料庫 | MySQL,PostgreSQL,SQLite,SQL Server | MongoDB |
遵循laravel命名約定
來源 PSR standards.
另外,遵循Laravel社群認可的命名約定:
物件 | 規則 | 更優的寫法 | 應避免的寫法 |
---|---|---|---|
控制器 | 單數 | ArticleController | |
路由 | 複數 | articles/1 | |
路由命名 | 帶點符號的蛇形命名 | users.show_active | |
模型 | 單數 | User | |
hasOne或belongsTo關係 | 單數 | articleComment | |
所有其他關係 | 複數 | articleComments | |
表單 | 複數 | article_comments | |
透視表 | 按字母順序排列模型 | article_user | |
資料表字段 | 使用蛇形並且不要帶表名 | meta_title | |
模型引數 | 蛇形命名 | $model->created_at | |
外來鍵 | 帶有_id字尾的單數模型名稱 | article_id | |
主鍵 | - | id | |
遷移 | - | 2017_01_01_000000_create_articles_table | |
方法 | 駝峰命名 | getAll | |
資源控制器 | table | store | |
測試類 | 駝峰命名 | testGuestCannotSeeArticle | |
變數 | 駝峰命名 | $articlesWithAuthor | |
集合 | 描述性的,複數的 | $activeUsers = User::active()->get() | |
物件 | 描述性的,單數的 | $activeUser = User::active()->first() | |
配置和語言檔案索引 | 蛇形命名 | articles_enabled | |
檢視 | 短橫線命名 | show-filtered.blade.php | |
配置 | 蛇形命名 | google_calendar.php | |
內容 (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) ? null : $object->relation->id |
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容器來建立例項 而不是直接new一個例項
建立新的類會讓類之間的更加耦合,使得測試越發複雜。請改用IoC容器或注入來實現。
例子:
$user = new User;
$user->create($request->validated());
複製程式碼
更優的寫法:
public function __construct(User $user)
{
$this->user = $user;
}
....
$this->user->create($request->validated());
複製程式碼
避免直接從 .env
檔案裡獲取資料
將資料傳遞給配置檔案,然後使用config()
幫助函式來呼叫資料
例子:
$apiKey = env('API_KEY');
複製程式碼
更優的寫法:
// config/api.php
'key' => env('API_KEY'),// Use the data
$apiKey = config('api.key');
複製程式碼
使用標準格式來儲存日期,用訪問器和修改器來修改日期格式
例子:
{{ 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 getSomeDateAttribute($date)
{
return $date->format('m-d');
}
// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}
複製程式碼
其他的一些好建議
永遠不要在路由檔案中放任何的邏輯程式碼。
儘量不要在Blade模板中寫原始 PHP 程式碼。
原文作者:ikidnapmyself