ログ日記

作業ログと日記とメモ

プログラムの分け方とディレクトリ構造

最近、プログラムのディレクトリレイアウトというかファイルを置く場所を変えようと試みている。



旧来のMVC的な構造だと、ControllerやModelのディレクトリを分ける、レイヤーごとにまとめる、という感じになると思う。
商品情報表示画面、その管理画面、CSVダウンロード機能があるとすると、

old1
├── src
│   ├── Controller
│   │   ├── Admin
│   │   │   ├── ProductController.php
│   │   │   └── ProductCsvController.php
│   │   └── ProductController.php
│   ├── Dto
│   │   └── ProductDto.php
│   ├── Model
│   │   ├── CsvModel.php
│   │   ├── ProductCsvModel.php
│   │   └── ProductModel.php
│   └── ViewModel
│       └── ProductBehavior.php
└── templates
    ├── admin
    │   └── product
    │       └── product.blade.php
    └── product.blade.php

こんな感じになると思う。
ModelはDomainとかServiceとかInfraとか、そういうのでも良い。

ここに、期間限定キャンペーン用商品に関する機能を追加するとしよう。
特殊な画面と特殊な処理なので、DBに数項目追加するといった対応はできないとする。
そうすると

old2
├── src
│   ├── Controller
│   │   ├── Admin
│   │   │   ├── CampaignProductCsvController.php
│   │   │   ├── ProductController.php
│   │   │   └── ProductCsvController.php
│   │   ├── CampaignProductController.php
│   │   └── ProductController.php
│   ├── Dto
│   │   ├── CampaignProductDto.php
│   │   └── ProductDto.php
│   ├── Model
│   │   ├── CampaignProductCsvModel.php
│   │   ├── CsvModel.php
│   │   ├── ProductCsvModel.php
│   │   └── ProductModel.php
│   └── ViewModel
│       ├── CampaignProductBehavior.php
│       └── ProductBehavior.php
└── templates
    ├── admin
    │   └── product
    │       ├── campaign_product.blade.php
    │       └── product.blade.php
    ├── campaign_product.blade.php
    └── product.blade.php

こんな感じのディレクトリレイアウト、クラス名、テンプレート名で機能追加するだろうか。

フレームワークなんかでは専用コマンドが用意されたりしていて、scaffoldと呼ばれたりもする。
これで一気にController、Model、Viewのファイルを生成したりするわけだけれども、そもそもの構造が複雑だからこそ専用コマンドが複雑になるとも言える。

自分はこの自動生成は好きではない。AbstractControllerやAbstractModelも好きではない。できるだけシンプルにしたい。
最近その辺りの構造を大きく変えようと思っている。


基本的には層でディレクトリを分けるのではなくて、画面単位で分ける。

new1
└── src
    ├── Model
    │   ├── CsvModel.php
    │   ├── ProductModel
    │   │   └── Dto.php
    │   └── ProductModel.php
    ├── Page
    │   ├── Admin
    │   │   └── Product
    │   │       ├── CsvModel.php
    │   │       ├── CsvPage.php
    │   │       ├── Dto.php
    │   │       ├── Html.php
    │   │       └── Page.php
    │   └── Product
    │       ├── Dto.php
    │       ├── Html.php
    │       └── Page.php
    └── ViewModel
        └── ProductBehavior.php

こんな感じにする。
ControllerはMVC的なコントロールはしないので単にPageという名前する。
テンプレートエンジンはやめて、Html.php に昔ながらのやり方で書く。とは言ってもネームスペースとクラスは使う。
Page.php から ModelやViewModelを使って何らかの処理をした後に、Dtoに詰め込んでHtml.php に渡す。これを同じディレクトリに置く。
2箇所以上で使う処理は旧来のModelやService等に分ける。1箇所からしか使われない処理はPageクラスの近くに置く。先のことは考えない。他のPageでの処理を使いたくなったその時に共通化して移動する。

キャンペーン機能を追加した後はこんな感じ。

new2
└── src
    ├── Model
    │   ├── CsvModel.php
    │   ├── ProductModel
    │   │   ├── CampaignInterface.php
    │   │   └── Dto.php
    │   └── ProductModel.php
    ├── Page
    │   ├── Admin
    │   │   └── Product
    │   │       ├── Campaign
    │   │       │   ├── CsvModel.php
    │   │       │   ├── CsvPage.php
    │   │       │   └── Html.php
    │   │       ├── CsvModel.php
    │   │       ├── CsvPage.php
    │   │       ├── Dto.php
    │   │       ├── Html.php
    │   │       └── Page.php
    │   └── Product
    │       ├── Campaign
    │       │   ├── Dto.php
    │       │   ├── Html.php
    │       │   └── Page.php
    │       ├── Dto.php
    │       ├── Html.php
    │       └── Page.php
    └── ViewModel
        ├── CampaignProductBehavior.php
        └── ProductBehavior.php

ここでも各レイヤーをまとめて同じディレクトリに追加する。この方が見やすく、依存関係も分かりやすい。
リファクタリングも手軽にできる。


前提として、DBで

select * from product

のようなことはしない、という要因もあるかもしれない。

select product_id, name, description from product

のように、必要な項目だけ取得する。
なおかつ、型指定ありのDtoに詰め替える。
逆に言うと、型指定ありのDtoで必要な項目だけ詰めて渡すようなことをやり始めると、あまり共通化ができない、とも言える。

Html.php もPHPStanで静的解析に含むので、かなり安心感がある。

もし共通化してDBの返却値も共通化したりすると、富豪的なDBアクセスとオブジェクト生成で遅くなる。
各画面でDBの同じテーブルにアクセスしたとしても、必要な項目まで同じということはあまり起こらない。それでも共通化するとしたら、各画面で必要な項目をどんどん追加していくことになってDB処理が膨れていく。
機能を削除したとしても、迂闊に項目を消したりできない。

そういった理由で、今は徐々にテンプレートエンジンを使うのをやめて各ページの処理とHTMLを同じ場所に移動したりして、徐々に変更して試している。