2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実際に使用しているLaravelベストプラクティス

Last updated at Posted at 2025-09-16

💡はじめに

この記事では、私が実務で使用しているLaravelのベストプラクティスを具体的なコード例とあわせていくつか紹介していきます。

👍こんな人におすすめ

  • Laravelを一通り触ったことがある方
  • 設計や可読性を意識したいと考えている人

🚩使用目的

Laravelは機能が豊富で柔軟性も高く、少ないコードでサクッとアプリを作れるのが大きな魅力です。

ただ、その便利さゆえにコントローラが肥大化してしまったり、ビジネスロジックがあちこちに散らばってしまったりと、コードの品質や保守性に課題が出やすいのも事実です。

プロジェクトが大きくなるにつれて可読性や拡張性が失われていき、バグや開発効率の低下につながることもあります。

だからこそ最初から品質や保守性を意識した書き方を取り入れることが大事だと感じています。

単一責任の原則

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

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;
    }
}

OK:

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クエリを使用する場合はレポジトリークラスに入れます。
NG:

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}

OK:

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

バリデーション

バリデーションはコントローラからリクエストクラスに移動させます。
NG:

public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);

    ...
}

OK:

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',
        ];
    }
}

繰り返し書かない (DRY)

可能であればコードを再利用します。単一責任の原則は重複を避けることに役立ちます。また、Bladeテンプレートを再利用したり、Eloquentのスコープなどを使用したりします。
NG:

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

OK:

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

JSとCSSをBladeの中に書かない、PHPクラスの中にHTMLを入れない

NG:

let article = `{{ json_encode($article) }}`;

OK:

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

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

Eloquentにより読みやすくメンテナンスしやすいコードを書くことができます。また、Eloquentには論理削除、イベント、スコープなどの優れた組み込みツールがあります。
NG:

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

OK:

Article::has('user.profile')->verified()->latest()->get();

マスアサインメント

NG:

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

OK:

$category->article()->create($request->validated());

Blade内でクエリを実行しない(N+1問題)

NG: 100ユーザに対して、101回のDBクエリが実行される

@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

OK: 100ユーザに対して、2回のDBクエリが実行される

$users = User::with('profile')->get();

@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach

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

NG:

$request->session()->get('cart');
$request->input('name');

OK:

session('cart');
$request->name;

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
テーブル 複数形 article_comments article_comment,articleComments
テーブルカラム スネークケース モデル名は含めない meta_title MetaTitle,article_meta_title
メソッド キャメルケース getAll get_all
変数 キャメルケース $articlesWithAuthor $articles_with_author
ビュー ケバブケース show-filtered.blade.php showFiltered.blade.php
コンフィグ スネークケース google_calendar.php googleCalendar.php

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

代わりにconfigファイルへデータを渡します。そして、アプリケーション内でデータを参照する場合はconfig()ヘルパー関数を使います。
NG:

$apiKey = env('abc123');

OK:

// config/api.php
'key' => env('API_KEY'),

// データを使用する
$apiKey = config('api.key');

まとめ

この記事では、私が実務で意識しているLaravelのベストプラクティスをコード例とともに紹介しました。

  • 単一責任の原則を意識して、コードを小さく整理する
  • ファットモデル・スキニーコントローラ で責務を明確にする
  • バリデーションをRequestディレクトリ配下へ移動して再利用性を高める
  • DRYの原則でコードの重複を避ける
  • Eloquentの活用とN+1問題の回避で読みやすくパフォーマンスの良い処理を書く
  • 可読性を意識した短い構文や命名規則を徹底する
  • 設定値は.envから直接呼ばずconfigを経由させる
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?