Skip to content

Latest commit

 

History

History
626 lines (447 loc) · 20 KB

japanese.md

File metadata and controls

626 lines (447 loc) · 20 KB

Laravel ベストプラクティス

You might also want to check out the real-world Laravel example application

翻訳:

Nederlands (by Protoqol)

한국어 (by cherrypick)

Українська (by Tenevyk)

Русский

فارسی (by amirhossein baghaie)

Português (by jonaselan)

Tiếng Việt (by Chung Nguyễn)

Español (by César Escudero)

Français (by Mikayil S.)

Polski (by Karol Pietruszka)

Türkçe (by Burak)

Deutsch (by Sujal Patel)

Italiana (by Sujal Patel)

العربية (by ahmedsaoud31)

Laravel example app

コンテンツ

単一責任の原則

ファットモデル、スキニーコントローラ

バリデーション

ビジネスロジックはサービスクラスの中に書く

繰り返し書かない (DRY)

クエリビルダや生のSQLクエリよりもEloquentを優先して使い、配列よりもコレクションを優先する

マスアサインメント

Bladeテンプレート内でクエリを実行しない。Eager Lodingを使う(N + 1問題)

コメントを書く。ただしコメントよりも説明的なメソッド名と変数名を付けるほうが良い

JSとCSSをBladeテンプレートの中に入れない、PHPクラスの中にHTMLを入れない

コード内の文字列の代わりにconfigファイルとlanguageのファイル、定数を使う

コミュニティに受け入れられた標準のLaravelツールを使う

Laravelの命名規則に従う

できるだけ短く読みやすい構文で書く

newの代わりにIoCコンテナもしくはファサードを使う

.envファイルのデータを直接参照しない

日付を標準フォーマットで保存する。アクセサとミューテータを使って日付フォーマットを変更する

その他 グッドプラクティス

単一責任の原則

クラスとメソッドは1つの責任だけを持つべきです。

Bad:

public function getFullNameAttribute(): string
{
    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(): string
{
    return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}

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

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

public function getFullNameShort(): string
{
    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');
        }
    }
}

🔝 コンテンツに戻る

繰り返し書かない (DRY)

可能であればコードを再利用します。単一責任の原則は重複を避けることに役立ちます。また、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();
}

🔝 コンテンツに戻る

クエリビルダや生のSQLクエリよりもEloquentを優先して使い、配列よりもコレクションを優先する

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());

🔝 コンテンツに戻る

Bladeテンプレート内でクエリを実行しない。Eager Lodingを使う(N + 1問題)

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())

🔝 コンテンツに戻る

JSとCSSをBladeテンプレートの中に入れない、PHPクラスの中にHTMLを入れない

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を使用することです。

🔝 コンテンツに戻る

コード内の文字列の代わりにconfigファイルとlanguageのファイル、定数を使う

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標準機能とコミュニティパッケージを使うことを推奨します。将来あなたと共に働くことになるどの開発者も新しいツールを学習する必要があります。また、サードパーティ製のパッケージやツールを使用している場合は、Laravelコミュニティから助けを得る機会が大幅に少なくなります。あなたのクライアントにその代金を払わせないでください。

タスク 標準ツール サードパーティ製ツール
認可 Policies Entrust, Sentinel または他のパッケージ
アセットコンパイル Laravel Mix, Vite Grunt, Gulp, サードパーティ製パッケージ
開発環境 Laravel Sail, Homestead Docker
デプロイ Laravel Forge Deployer またはその他ソリューション
単体テスト PHPUnit, Mockery Phpspec, Pest
ブラウザテスト Laravel Dusk Codeception
DB Eloquent SQL, Doctrine
テンプレート Blade Twig
データの取り扱い Laravel collections Arrays
フォームバリデーション Request classes サードパーティ製パッケージ、コントローラ内でバリデーション
認証 標準組み込み サードパーティ製パッケージ、独自実装
API 認証 Laravel Passport, Laravel Sanctum サードパーティ製の 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

🔝 コンテンツに戻る

Laravelの命名規則に従う

PSRに従います。

また、Laravelコミュニティに受け入れられた命名規則に従います。

対象 規則 Good Bad
コントローラ 単数形 ArticleController ArticlesController
ルート 複数形 articles/1 article/1
名前付きルート スネークケースとドット表記 users.show_active users.show-active, show-active-users
モデル 単数形 User Users
hasOne または belongsTo 関係 単数形 articleComment articleComments, article_comment
その他すべての関係 複数形 articleComments articleComment, article_comments
テーブル 複数形 article_comments article_comment, articleComments
Pivotテーブル 単数形 モデル名のアルファベット順 article_user user_article, articles_users
テーブルカラム スネークケース モデル名は含めない meta_title MetaTitle; article_meta_title
モデルプロパティ スネークケース $model->created_at $model->createdAt
外部キー 単数形 モデル名の最後に_idをつける article_id ArticleId, id_article, articles_id
主キー - id custom_id
マイグレーション - 2017_01_01_000000_create_articles_table 2017_01_01_000000_articles
メソッド キャメルケース getAll get_all
リソースコントローラのメソッド 一覧 store saveArticle
テストクラスのメソッド キャメルケース testGuestCannotSeeArticle test_guest_cannot_see_article
変数 キャメルケース $articlesWithAuthor $articles_with_author
コレクション 説明的、 複数形 $activeUsers = User::active()->get() $active, $data
オブジェクト 説明的, 単数形 $activeUser = User::active()->first() $users, $obj
設定ファイルと言語ファイルのインデックス スネークケース articles_enabled ArticlesEnabled; articles-enabled
ビュー ケバブケース show-filtered.blade.php showFiltered.blade.php, show_filtered.blade.php
コンフィグ スネークケース google_calendar.php googleCalendar.php, google-calendar.php
契約 (インターフェイス) 形容詞または名詞 AuthenticationInterface Authenticatable, IAuthentication
Trait 形容詞 Notifiable NotificationTrait
Trait (PSR) adjective NotifiableTrait Notification
Enum singular UserType UserTypes, UserTypeEnum
FormRequest singular UpdateUserRequest UpdateUserFormRequest, UserFormRequest, UserRequest
Seeder singular UserSeeder UsersSeeder

🔝 コンテンツに戻る

できるだけ短く読みやすい構文で書く

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コンテナもしくはファサードを使う

new構文はクラス間の密結合を生み出し、テストすることを難しくします。IoCコンテナまたはファサードを代わりに使います。

Bad:

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

Good:

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

...

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

🔝 コンテンツに戻る

.envファイルのデータを直接参照しない

代わりに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 $casts = [
    'ordered_at' => 'datetime',
];

public function getSomeDateAttribute($date)
{
    return $date->format('m-d');
}

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

🔝 コンテンツに戻る

その他 グッドプラクティス

ルートファイルにはロジックを入れないでください。

Bladeテンプレートの中でVanilla PHP(標準のPHPコードを記述すること)の使用は最小限にします。

🔝 コンテンツに戻る