本文翻譯改編自 Laravel 的十八個最佳實踐php
這篇文章並非什麼由 Laravel 改編的 SOLID 原則、模式等。css
只是爲了讓你注意你在現實生活的 Laravel 項目中最常忽略的內容。html
一個類和一個方法應該只有一個職責。
錯誤的作法:前端
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; } }
推薦的作法:laravel
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; }
若是你使用查詢構造器或原始 SQL 來查詢,請將全部與數據庫相關的邏輯放入 Eloquent 模型或存儲庫類中。git
壞:github
public function index() { $clients = Client::verified() ->with(['orders' => function ($q) { $q->where('created_at', '>', Carbon::today()->subWeek()); }]) ->get(); return view('index', ['clients' => $clients]); }
好:sql
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(); } }
將驗證從控制器移動到請求類。數據庫
很常見但不推薦的作法:json
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', ]; } }
一個控制器必須只有一個職責,所以應該將業務邏輯從控制器移到服務類。
壞:
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'); } } }
儘量重用代碼。 SRP(單一職責原則)正在幫助你避免重複。固然,這也包括了 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 能夠編寫可讀和可維護的代碼。此外,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->all());
很差的地方在於,這對於100 個用戶來講,等於執行 101 個 DB 查詢:
[@foreach](https://laravel-china.org/users/5651) (User::all() as $user) {{ $user->profile->name }} @endforeach
下面的作法,對於 100 個用戶來講,僅僅只執行 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())
壞:
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 中這樣來傳輸數據:
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'));
最好使用內置的 Laravel 功能和社區軟件包,而不是其餘第三方軟件包和工具。由於未來與你的應用程序一塊兒工做的開發人員都須要學習新的工具。另外,使用第三方軟件包或工具的話,若是遇到困難,從 Laravel 社區得到幫助的機會會大大下降。不要讓你的客戶爲此付出代價!
任務 | 標準工具 | 第三方工具 |
---|---|---|
受權 | Policies | Entrust, Sentinel and other packages |
前端編譯 | Laravel Mix | Grunt, Gulp, 3rd party packages |
開發環境 | Homestead | Docker |
部署 | Laravel Forge | Deployer and other solutions |
單元測試 | PHPUnit, Mockery | Phpspec |
瀏覽器測試 | Laravel Dusk | Codeception |
數據庫操做 | Eloquent | SQL, Doctrine |
模板 | Blade | Twig |
數據操做 | Laravel collections | Arrays |
表單驗證 | Request classes | 3rd party packages, validation in controller |
認證 | Built-in | 3rd party packages, your own solution |
API 認證 | Laravel Passport | 3rd party JWT and OAuth packages |
建立 API | Built-in | Dingo API and similar packages |
數據庫結構操做 | 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 |
生成測試數據 | Laravel Task Scheduler | Scripts and 3rd party packages |
數據庫 | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
遵循 PSR 標準。 另外,請遵循 Laravel 社區接受的命名約定:
類型 | 規則 | 正確示例 | 錯誤示例 |
---|---|---|---|
Controller | 單數 | ArticleController | |
Route | 複數 | articles/1 | |
Named route | 帶點符號的蛇形命名 | users.show_active | |
Model | 單數 | User | |
hasOne or belongsTo relationship | 單數 | articleComment | |
All other relationships | 複數 | articleComments | |
Table | 複數 | article_comments | |
Pivot table | 按字母順序排列的單數模型名稱 | article_user | |
Table column | 帶着模型名稱的蛇形命名 | meta_title | |
Foreign key | 帶_id後綴的單數型號名稱 | 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 | 具描述性的複數形式 | $activeUsers = User::active()->get() | |
Object | 具描述性的單數形式 | $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') |
新的 Class 語法建立類時,不只使得類與類之間緊密耦合,還加劇了測試的複雜度。推薦改用 IoC 容器或 facades。
壞:
$user = new User; $user->create($request->all());
好:
public function __construct(User $user) { $this->user = $user; } .... $this->user->create($request->all());
.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 getMonthDayAttribute($date) { return $date->format('m-d'); } // View {{ $object->ordered_at->toDateString() }} {{ $object->ordered_at->monthDay }}
更多現代化 PHP 知識,請前往 Laravel / PHP 知識社區