8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel9 の app ディレクトリ構成はこれに決まりだ!

Last updated at Posted at 2023-01-31

はじめに

みなさん、初めまして!リバネス開発チームのトミー(@tomyf)です。
最近弊社の求人を出すようになりましたので、少しでも開発のイメージがつきやすいように書いていきたいと思います!

弊社では、アプリケーションの一部に Laravel を使用しています。
Laravel を初期インストールした段階で、ある程度ディレクトリ分けされていますが、そこから先は開発チームの方針や開発者個人によって個性が出てくるところだと思います。

本記事では、私達の開発チームの Laravel の使い方を載せていきますので、「拙者はこうしている!」や「真似したい!」など気軽にコメントください😆

※ セキュリティの観点から、具体的なディレクトリ名やファイル名は別の名称で置き換えています。

環境

  • Docker v20.10.17
  • Laravel v9.25.1

app ディレクトリについて

アプリケーションのメインになる部分ですね!多くの人がコントローラにロジックを書いていくことになると思います。慣れて来るとフォームリクエストにバリデーションを分割したり、サービス層を作ったり、モデルにロジックを書いたりと工夫されたりしてますよね?

私達の開発チームでは、以下のようなディレクトリ構成にしています!
ここでは、ユーザモデル User とプロジェクトモデル Project があると仮定しています。

※ 変更のない ConsoleExceptionsProviders は省略しています。

app/
├─ Http/
│  ├─ Controllers/
│  │  ├─ WithToken/
│  │  │  ├─ ProjectController.php
│  │  │  └─ GetAuthUser.php
│  │  └─ WithoutToken/
│  │     └─ GetPublicProjects.php
│  │
│  ├─ Middleware/
│  │  └─  Authenticate.php
│  │
│  ├─ Requests/
│  │  ├─ WithToken/
│  │  │  ├─ Project/
│  │  │  │  ├─ IndexRequest.php
│  │  │  │  └─ StoreRequest.php
│  │  │  └─ GetAuthUserRequest.php
│  │  └─ WithoutToken/
│  │     └─ GetPublicProjectsRequest.php
│  │
│  └─ Resources/
│     ├─ ProjectCollection.php
│     ├─ ProjectResource.php
│     ├─ PublicProjectCollection.php
│     ├─ PublicProjectResource.php
│     ├─ UserCollection.php
│     └─ UserResource.php
│
├─ Models/
│  ├─ Concerns/
│  │  ├─ HasProjectRelationships.php
│  │  └─ HasUuidAttributes.php
│  ├─ Project.php
│  └─ User.php
│
├─ Services/
│  ├─ MediaLiveManager.php
│  └─ S3Manager.php
│
├─ UseCases/
│  └─ Project/
│     ├─ GetPublicProjects.php
│     ├─ GetProjectsByUser.php
│     └─ CreateProject.php
│
└─ ValueObjects/
   └─ WhereValue.php

Controllers

コントローラですが、ユーザ認証のトークンが必要かどうかでディレクトリを区切っています。 API パスの段階で分かれるようにしているため、それに合わせてディレクトリも分けている形となっています。
基本的にリソースコントローラは WithToken 側に作成することになり、 WithoutToken には GET メソッドのシングルアクションコントローラを作成しています。
シングルアクションコントローラのファイル名は、関数名の命名規則に準拠していますが、もっと良い名付けがあればコメントください!

こちらはプロジェクトを新規作成する場合のコントローラの例です。

ProjectController.php
use App\Http\Controllers\Controller;
use App\Http\Requests\WithToken\Project\StoreRequest;
use App\Http\Resources\ProjectResource;
use App\UseCases\Project\CreateProject;

class ProjectController extends Controller
{
    public function store(StoreRequest $request, CreateProject $createProject)
    {
        $user = $request->user();
        $newProject = $request->makeProject();
        $project = $createProject($newProject, $user);

        return new ProjectResource($project);
    }
}

Requests

フォームリクエストでは、ユーザが対象のファイルの権限を持っているかやバリデーションの検証を行っています。
また、リソースの作成に関する API の場合は、ここでモデルのインスタンスを作成する処理を行っています。
それぞれのコントローラに対応するようにディレクトリを区切っています。リソースコントローラの場合は、リソース名でディレクトリを作成し、シングルアクションコントローラの場合は、アクション名 + Request を付けるようにしています。

こちらはプロジェクトを新規作成する場合のコントローラのフォームリクエストの例です。

StoreRequest.php
use App\Models\Project;
use Illuminate\Foundation\Http\FormRequest;

class StoreRequest extends FormRequest
{
    public function rules()
    {
        return [
            'name' => ['required', 'string']
        ];
    }

    public function makeProject()
    {
        return new Project($this->validated());
    }
}

Resources

コントローラのレスポンスは、全て API リソースを通して返すようにしています。 ProjectResourcePublicProjectResource がありますが、これは本人にしか見れない情報が別のユーザに表示されないように、明示的に分けています。

こちらはプロジェクトモデルを基に作成した API リソースの例です。

ProjectResource.php
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Arr;

class ProjectResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            ...Arr::only(parent::toArray($request), [
                'id',
                'uuid',
                'user_id',
                'name',
            ]),
            'user' => new UserResource($this->whenLoaded('user')),
        ];
    }
}

Models / Concerns

この Concerns が気になっている人もいるのではないでしょうか。ここはモデルの機能を拡張する trait を定義したファイルを入れています。ここでは2種類のファイルがあり、一つは「モデルに UUID があることを定義し、新規作成時に自動で生成して格納する」と言った汎用的なものと、「特定のモデルに関連するリレーションを定義する」と言った専用のものがあります。

汎用的な trait がこちらです。

HasUuidAttributes.php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

trait HasUuidAttributes
{
    public static function bootHasUuidAttributes()
    {
        static::creating(function (Model $model) {
            $model->generateUuid();
        });
    }

    public function generateUuid()
    {
        $uuid = Str::orderedUuid()->toString();

        $this->setUuid($uuid);
    }

    public function setUuid($value)
    {
        $this->{$this->getUuidColumn()} = $value;

        return $this;
    }

    public function getUuidColumn()
    {
        return static::UUID;
    }
}

プロジェクトモデル専用の trait がこちらです。

HasProjectRelationships.php
use App\Models\User;

trait HasProjectRelationships
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

trait の使用方法はこのように記載します。

use App\Models\Concerns\HasProjectRelationships;
use App\Models\Concerns\HasUuidAttributes;

class ProjectEntry extends Model
{
    use HasUuidAttributes, HasProjectRelationships

    const UUID = 'uuid';
}

Services

ここでは一般的に言われるサービス層とは少し違う意図で作成しています。
主に外部サービスとの連携に関する処理をまとめたファイルを配置しています。直接データベースに接続してデータを取得する処理はありません。

UseCases

このユースケースに、データベースからデータ取得やデータ作成を行う処理を書いていきます。ドメイン毎にディレクトリを区切って、全てシングルアクションメソッドで作成しています。

GetProjectsByUser.php
use App\Models\User;

class GetProjectsByUser
{
    public function __invoke(User $user)
    {
        $projects = $user->projects()->latest()->paginate();
        $projects->load('user');

        return $projects;
    }
}
CreateProject.php
use App\Models\Project;
use App\Models\User;

class CreateProject
{
    public function __invoke(Project $project, User $user)
    {
        $user->projects()->save($project);
        $project->load('user');

        return $project;
    }
}

ValueObjects

バリューオブジェクトには、システム全体で共通して使用したいデータ形式を定義しています。主な用途としては、データ取得時の条件をまとめた配列をユースケースの引数に渡したい時に使用します。

おわりに

今回は Laravel のディレクトリ構成について紹介でした!
これから新しいプロジェクトで Laravel を使うか迷っている方や Laravel を使ってプロジェクトを立ち上げる予定の方の助けになれば嬉しいです。
開発が進んでいけば、ディレクトリ構成が変わる事もあると思いますので、その都度更新していきたいと思います。

こちらのディレクトリ構成に辿り着くまでに @mpyw さんの 5年間 Laravel を使って辿り着いた,全然頑張らない「なんちゃってクリーンアーキテクチャ」という落としどころ がとても参考になりました!

8
5
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
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?