4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Laravelベストプラクティスで実際の開発で使ってる箇所まとめ

Last updated at Posted at 2024-06-13

はじめに

Laravelベストプラクティスを改めて読んでどのプロジェクトでも認識すべきルールを軸にまとめてみました
逆に「プロジェクトによって運用方法が変わる」ような箇所は割愛した箇所は後述でリストにしてみました

Laravelベストプラクティスの目的

Laravelは強力で使いやすいPHPフレームワークですが、
ベストプラクティスに従うことで、その真価を最大限に引き出すことができます。
このガイドでは、Laravelのコーディングスタイルや設計パターンにおけるベストプラクティスを紹介し、コードの品質向上、保守性の向上、そして開発効率の向上を目指します。

単一の原則

クラスとメソッドは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:

Controller.php

class Controller extends Controller
{

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

Good:

Controller.php

class Controller extends Controller
{

    public function store(PostRequest  $request)
    {
        ...
    }
}
PostRequest.php
class PostRequest extends Request
{
    public function rules()
    {
        return [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
            'publish_at' => 'nullable|date',
        ];
    }

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

コントローラは1つの責務だけを持たないといけません、
そのためビジネスロジックはコントローラからサービスクラスに移動させます

リクエストを受け取ってViewなりjsonなりを別の処理へ渡すのがコントローラの責務ですので
それ以外の機能的な部分はサービスクラスで実装しようねって話ですね

Bad:

Controller.php

class Controller extends Controller
{

    public function store(PostRequest  $request)
    {
        if ($request->hasFile('image')) {
            $request->file('image')->move(public_path('images') . 'temp');
        }
    
        ...
    }
}

Good:

Controller.php

class Controller extends Controller
{

    public function store(PostRequest  $request)
    {
        $this->articleService->handleUploadedImage($request->file('image'));
        ...
    }
}
ArticleService.php
class ArticleService
{
    public function handleUploadedImage($image)
    {
        if (!is_null($image)) {
            $image->move(public_path('images') . 'temp');
        }
    }
}

繰り返し書かない (DRY)

可能であればコードを再利用します
単一責任の原則は重複を避けることに役立ちます

Bad:

// 下記の->where('verified', 1)->whereNotNull('deleted_at')->が重複してしまっている
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を使うことで、読みやすくメンテナンスしやすいコードを書けます

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

マスアサインメント

Eloquentのマスアサインメント機能を使ってデータの保存を簡潔にします。

Bad:

$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
$article->category_id = $category->id;
$article->save();

Good:

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

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

クエリの効率化のため、Eager Loadingを使用します

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

@foreach (User::all() /** ←で1回 **/ as $user)
    {{ $user->profile->name }} /** リレーションを使わなければユーザーの数だけprofileテーブルを参照する 100ユーザーいれば100回 **/
@endforeach

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

// 最初のクエリで全ユーザーを取得し、2回目のクエリで各ユーザーのプロファイルを一度に取得する
$users = User::with('profile')->get(); 

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

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

コメントを適切に使いつつ、コード自体が説明的であるようにします

Bad:

if (count((array) $builder->getQuery()->joins) > 0)

Better:

// 参加者がいるかどうかを判断する
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();

コード内の文字列の代わりに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のヘルパー関数を活用してコードを簡潔にします

Bad:

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

Good:

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

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

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()ヘルパー関数を使用します

Bad:

$apiKey = env('API_KEY');

Good:

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

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

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

日付フォーマットの管理を容易にするため、Eloquentの機能を活用します

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の使用は最小限にする。

割愛箇所

  • コミュニティに受け入れられた標準のLaravelツールを使う
  • Laravelの命名規則に従う

上記は参画されるプロジェクトの開発ルールに従ってほしいという意図で割愛させていただきました

おわりに

このガイドを参考にして、Laravelでの開発を効率的に進めてみましょう
ベストプラクティスに従うことで、コードの品質が向上し、メンテナンスが容易になります
チーム開発になるとその恩恵をより一層感じるかと思います
また、従うことでどのようなメリットが生まれるのかを自分なりに考えて発信してみるのも良いかもしれませんね

4
6
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
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?