翻訳:
Nederlands (by Protoqol)
한국어 (by cherrypick)
Українська (by Tenevyk)
فارسی (by amirhossein baghaie)
Tiếng Việt (by Chung Nguyễn)
Español (by César Escudero)
Français (by Mikayil S.)
Polski (by Maciej Jeziorski)
Deutsch (by Sujal Patel)
Italiana (by Sujal Patel)
العربية (by ahmedsaoud31)
これはSOLID原則やパターンなどをLavavelに適用させたものではありません。 ここでは、実際のLaravelプロジェクトでは通常無視されるベストプラクティスを見つけることができます。
クエリビルダや生のSQLクエリよりもEloquentを優先して使い、配列よりもコレクションを優先する
Bladeテンプレート内でクエリを実行しない。Eager Lodingを使う(N + 1問題)
コメントを書く。ただしコメントよりも説明的なメソッド名と変数名を付けるほうが良い
JSとCSSをBladeテンプレートの中に入れない、PHPクラスの中にHTMLを入れない
コード内の文字列の代わりにconfigファイルとlanguageのファイル、定数を使う
コミュニティに受け入れられた標準のLaravelツールを使う
日付を標準フォーマットで保存する。アクセサとミューテータを使って日付フォーマットを変更する
クラスとメソッドは1つの責任だけを持つべきです。
Bad:
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;
}
}
Good:
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;
}
DBに関連するすべてのロジックはEloquentモデルに入れるか、もしクエリビルダもしくは生のSQLクエリを使用する場合はレポジトリークラスに入れます。
Bad:
public function index()
{
$clients = Client::verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
return view('index', ['clients' => $clients]);
}
Good:
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();
}
}
バリデーションはコントローラからリクエストクラスに移動させます。
Bad:
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
....
}
Good:
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',
];
}
}
コントローラはただ1つの責任だけを持たないといけません、そのためビジネスロジックはコントローラからサービスクラスに移動させます。
Bad:
public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}
....
}
Good:
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');
}
}
}
可能であればコードを再利用します。単一責任の原則は重複を避けることに役立ちます。また、Bladeテンプレートを再利用したり、Eloquentのスコープなどを使用したりします。
Bad:
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();
}
Good:
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には論理削除、イベント、スコープなどの優れた組み込みツールがあります。
Bad:
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
Good:
Article::has('user.profile')->verified()->latest()->get();
Bad:
$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();
Good:
$category->article()->create($request->validated());
Bad (100ユーザに対して、101回のDBクエリが実行される):
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
Good (100ユーザに対して、2回のDBクエリが実行される):
$users = User::with('profile')->get();
...
@foreach ($users as $user)
{{ $user->profile->name }}
@endforeach
Bad:
if (count((array) $builder->getQuery()->joins) > 0)
Better:
// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)
Good:
if ($this->hasJoins())
Bad:
let article = `{{ json_encode($article) }}`;
Better:
<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();
もっとも良い方法は、データを転送するためJSパッケージに特別なPHPを使用することです。
Bad:
public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');
Good:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
サードパーティ製のパッケージやツールの代わりに、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 collections | Arrays |
フォームバリデーション | Request classes | サードパーティ製パッケージ、コントローラ内でバリデーション |
認証 | 標準組み込み | サードパーティ製パッケージ、独自実装 |
API 認証 | Laravel Passport | サードパーティ製の JWT や OAuth パッケージ |
API作成 | 標準組み込み | Dingo API や類似パッケージ |
DB構造の取り扱い | Migrations | 直接DB構造を扱う |
ローカライゼーション | 標準組み込み | サードパーティ製パッケージ |
リアルタイムユーザインターフェース | Laravel Echo, Pusher | サードパーティ製パッケージ または直接Webソケットを扱う |
テストデータ生成 | Seeder classes, Model Factories, Faker | 手動でテストデータを作成 |
タスクスケジューリング | Laravel Task Scheduler | スクリプトやサードパーティ製パッケージ |
DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
PSRに従います。
また、Laravelコミュニティに受け入れられた命名規則に従います。
対象 | 規則 | Good | Bad |
---|---|---|---|
コントローラ | 単数形 | ArticleController | |
ルート | 複数形 | articles/1 | |
名前付きルート | スネークケースとドット表記 | users.show_active | |
モデル | 単数形 | User | |
hasOne または belongsTo 関係 | 単数形 | articleComment | |
その他すべての関係 | 複数形 | articleComments | |
テーブル | 複数形 | article_comments | |
Pivotテーブル | 単数形 モデル名のアルファベット順 | article_user | |
テーブルカラム | スネークケース モデル名は含めない | meta_title | |
モデルプロパティ | スネークケース | $model->created_at | |
外部キー | 単数形 モデル名の最後に_idをつける | article_id | |
主キー | - | id | |
マイグレーション | - | 2017_01_01_000000_create_articles_table | |
メソッド | キャメルケース | getAll | |
リソースコントローラのメソッド | 一覧 | store | |
テストクラスのメソッド | キャメルケース | testGuestCannotSeeArticle | |
変数 | キャメルケース | $articlesWithAuthor | |
コレクション | 説明的、 複数形 | $activeUsers = User::active()->get() | |
オブジェクト | 説明的, 単数形 | $activeUser = User::active()->first() | |
設定ファイルと言語ファイルのインデックス | スネークケース | articles_enabled | |
ビュー | ケバブケース | show-filtered.blade.php | |
コンフィグ | スネークケース | google_calendar.php | |
契約 (インターフェイス) | 形容詞または名詞 | Authenticatable | |
Trait | 形容詞 | Notifiable |
Bad:
$request->session()->get('cart');
$request->input('name');
Good:
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') |
new構文はクラス間の密結合を生み出し、テストすることを難しくします。IoCコンテナまたはファサードを代わりに使います。
Bad:
$user = new User;
$user->create($request->validated());
Good:
public function __construct(User $user)
{
$this->user = $user;
}
....
$this->user->create($request->validated());
代わりにconfigファイルへデータを渡します。そして、アプリケーション内でデータを参照する場合はconfig()
ヘルパー関数を使います。
Bad:
$apiKey = env('API_KEY');
Good:
// config/api.php
'key' => env('API_KEY'),
// データを使用する
$apiKey = config('api.key');
Bad:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
Good:
// 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テンプレートの中でVanilla PHP(標準のPHPコードを記述すること)の使用は最小限にします。