こんにちは、かつコーチです。
これまでService・Repositoryパターン、Form Requestと、責務ごとにクラスを分ける方法を紹介してきました。
クラスの数が増えてくると、次に気になるのが「これらをどこに置くか」というディレクトリ構成の問題です。
今回は、Laravelのデフォルト構成の限界と、カスタマイズする際の考え方を整理します。
デフォルトのディレクトリ構成の限界
app配下がControllerだらけになる問題
Laravelのデフォルト構成では、app/Http/Controllers にControllerが集中します。
そこにこれまで紹介してきたServiceやRepositoryを素朴に追加していくと、app 直下がこんな状態になりがちです。
app/
├── Http/
│ ├── Controllers/
│ │ ├── PostController.php
│ │ ├── CommentController.php
│ │ └── CategoryController.php
│ └── Requests/
│ ├── StorePostRequest.php
│ └── UpdatePostRequest.php
├── Services/
│ ├── PostService.php
│ └── CommentService.php
├── Repositories/
│ ├── PostRepositoryInterface.php
│ ├── EloquentPostRepository.php
│ └── ...
└── Models/
├── Post.php
└── Comment.php
一見整理されているようですが、「投稿(Post)」に関する機能を探そうとすると、Controller・Request・Service・Repository・Modelの5つのフォルダを横断しなければなりません。
機能数が増えるほど、この「1つの機能を見るために複数フォルダを行き来する」コストが積み重なっていきます。
種類別か機能別か
これは「技術的な種類(レイヤー)でフォルダを分けるか」「機能(ドメイン)でフォルダを分けるか」という、設計の考え方の違いに起因します。
デフォルトのLaravel構成は前者、つまりレイヤーごとにまとめる考え方が土台になっています。
カスタマイズの考え方
レイヤーごとにまとめる(デフォルトに近い発展形)
小〜中規模のプロジェクトでは、デフォルトのレイヤー分けを維持しつつ、Service・Repositoryのフォルダを整えるだけでも十分です。
app/
├── Http/Controllers/
├── Services/
├── Repositories/
└── Models/
この構成のメリットは、Laravelの標準的なやり方に近く、他の開発者が見てもすぐに理解できる点です。
機能ごとにまとめる(Feature-based)
一方、機能数が多い中〜大規模プロジェクトでは、「機能(ドメイン)ごとにフォルダをまとめる」構成が有効になる場合があります。
app/
├── Domain/
│ ├── Post/
│ │ ├── PostController.php
│ │ ├── PostService.php
│ │ ├── PostRepositoryInterface.php
│ │ ├── EloquentPostRepository.php
│ │ └── Post.php
│ └── Comment/
│ ├── CommentController.php
│ ├── CommentService.php
│ └── Comment.php
「投稿に関するものはすべて Domain/Post を見ればいい」という状態になり、機能単位での見通しが良くなります。
判断軸をまとめると、次のようになります。
| 観点 | レイヤー別 | 機能別(Domain) |
|---|---|---|
| 学習コスト | 低い(標準的) | やや高い(独自ルールの理解が必要) |
| 機能追加時の見通し | 複数フォルダを横断 | 1フォルダで完結しやすい |
| 向いている規模 | 小〜中規模 | 中〜大規模、機能数が多い場合 |
実装例
Service/Repositoryフォルダの配置と名前空間
機能別に構成を変える場合、名前空間(namespace)もディレクトリ構成に合わせて変更する必要があります。
// app/Domain/Post/PostService.php
namespace App\Domain\Post;
use App\Domain\Post\PostRepositoryInterface;
class PostService
{
public function __construct(
private PostRepositoryInterface $postRepository
) {}
// ...
}
// app/Domain/Post/PostController.php
namespace App\Domain\Post;
use App\Http\Controllers\Controller;
use App\Http\Requests\StorePostRequest;
class PostController extends Controller
{
public function __construct(
private PostService $postService
) {}
// ...
}
名前空間をディレクトリ構成と一致させることで、PSR-4のオートロードが正しく機能します。
composer.jsonのautoload設定
app/ フォルダ自体はデフォルトで App\ 名前空間にマッピングされているため、app/Domain/Post/PostService.php を App\Domain\Post\PostService として扱う分には、追加の設定は不要です。
ただし、app/ の外側(例えば src/ フォルダなど)に独自のディレクトリを作る場合は、composer.json の autoload セクションを編集する必要があります。
{
"autoload": {
"psr-4": {
"App\\": "app/",
"Domain\\": "src/Domain/"
}
}
}
編集後は、必ずオートローダーの再生成コマンドを実行します。
composer dump-autoload
つまずきポイント:namespaceとautoloadのズレでハマった話
かつコーチが実際につまずいた話
以前、既存プロジェクトのディレクトリ構成をレイヤー別から機能別に移行していたとき、原因不明の「Class not found」エラーに何度も遭遇しました。
❌ Before:ファイルを移動しただけでnamespaceを直していない
// app/Domain/Post/PostService.php に移動したのに、namespaceが古いまま
namespace App\Services; // ファイルの実際の場所と一致していない
class PostService
{
// ...
}
ファイルだけを新しいフォルダに移動して、namespace 宣言の書き換えを忘れていました。
PHPのオートローダーは「クラス名(名前空間)」と「ファイルパス」が一致していることを前提に動いているため、ズレがあると Class "App\Domain\Post\PostService" not found のようなエラーになります。
さらに厄介だったのが、composer dump-autoload を実行し忘れていたことです。
Laravelは本番環境などでオートロードの結果をキャッシュしているため、ファイルを移動しただけではキャッシュが更新されず、しばらく古い場所を参照し続けてしまいました。
✅ After:ファイル移動時はnamespaceも同時に修正し、autoloadを再生成する
// app/Domain/Post/PostService.php
namespace App\Domain\Post; // ファイルの場所と一致させる
class PostService
{
// ...
}
# ファイル移動・namespace変更のあとは必ず実行する
composer dump-autoload
「ファイルを移動したら、namespaceの修正とオートロードの再生成をセットで行う」という手順を徹底するようになってから、この種のエラーには遭遇しなくなりました。
応用:チームでの合意形成と過度なカスタマイズのリスク
チームでの合意形成が前提
ディレクトリ構成のカスタマイズは、一人のプロジェクトなら好きに決められますが、チーム開発では話が変わってきます。
Laravelの標準構成から大きく外れるほど、新しく参加したメンバーが「どこに何があるか」を理解するまでの学習コストが上がります。
導入する前には、必ずチーム内でメリット・デメリットを共有し、合意を取ってから進めるべきです。
過度なカスタマイズのリスク
「きれいな構成にしたい」という気持ちが先行しすぎると、抽象化のためのレイヤーばかりが増え、逆にコードを追いにくくなることがあります。
ディレクトリ構成の変更は、あくまで「今のプロジェクトの規模・チーム体制に対して適切かどうか」を基準に判断するのがおすすめです。
迷ったときは、まずデフォルトに近い構成から始め、実際に困ってから少しずつカスタマイズしていく、というくらいがちょうど良いバランスだと感じています。
まとめ
この記事のポイント
- デフォルトのレイヤー別構成は、機能数が増えると1機能を追うのに複数フォルダを横断するコストが上がる
- レイヤー別と機能別(Domain)、それぞれにメリット・デメリットがあり、プロジェクト規模で判断する
- ファイルを移動する際は、namespaceの修正と
composer dump-autoloadの実行をセットで行う - チーム開発では、構成変更前に必ずメンバー間で合意形成を行う
次に読むべき記事
ここまでで、Controller・Service・Repository・Form Request・ディレクトリ構成という設計の基礎が一通りそろいました。
次回からは、実際にLaravel×Vue(Inertia.js)を使って、簡単なTODOアプリを一緒に作っていきます。
→ 次の記事:Laravel×Vue/Inertiaで簡単なTODOアプリを作ってみる