3
3

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 3 years have passed since last update.

【CakePHP】CakePHP3でDIをやりたい【Componentを駆使して失敗した話】

Last updated at Posted at 2020-02-17

# 問題定義

CakePHPでもUseCase層やService層を作りたい

CakePHP3ではデフォルトで下記のようなディレクトリ構成となっています。

src
├── Controller
│   └── Component
├── Model
│   ├── Behavior
│   ├── Entity
│   └── Table
├── Template
├── View
│  ...

基本的には、ControllerからComponentを、ComponentからEntityを操作することになるかと思いますが、
愚直に実装を進めていくとComponentが肥大化していってしまいます。

そのためUseCase層やService層が欲しくなってきます。
ControllerやModelと同階層に実装しようとすると下記のようになります。

src
├── Controller
│   └── Component
├── Model
│   ├── Behavior
│   ├── Entity
│   └── Table
├── UseCase
├── Service
├── Template
├── View
│  ...

一般的な構造ですが、CakePHPでこのように実装した場合、下記のような問題が発生します。

  • 既存のComponentの呼び出し方法が変わる
    • $this->MyComponent = new MyComponent(new ComponentRegistry());
  • UseCase等の呼び出し方法が長くなる
    • (new HogehogeUseCase())->action();

$this->hogehogeUseCase->action();
といった、cakePHPっぽい書き方はできないでしょうか?

とりあえずの解決策

UseCaseディレクトリ等をController\Componentディレクトリの下に作成するとスムーズに動かすことができました。

ディレクトリ構成

src
├── Controller
│   └── Component
│         ├── UseCase
│         │      └── HogehogeUseCase.php
│         └── Service
│               └── FugafugaService.php
├── Model
│   ├── Behavior
│   ├── Entity
│   └── Table
├── Template
├── View
│  ...

呼び出し方

class HomeController extends AppController {
    public $components = [
        'HogehogeUseCase' => ['className' => 'UseCase/HogehogeUseCase'],
        'FugafugaService' => ['className' => 'Service/FugafugaService'],
    ];

    public function index()
    {
        return $this->HogehogeUseCase->action();
    }
}

問題点

Controllerの下に収められてしまうことの違和感(問題度:小)

シンボリックリンクを張れば別階層に置けますが…

インターフェースの置き場所どうする?(問題度:小)

こんな感じになるでしょうか…

src
├── Controller
│   └── Component
│         ├── Interface
│         │   ├── UseCase
│         │   │    └── HogeUseCaseInterface.php
│         │   └── Service
│         │        └── FugaServiceInterface.php
│         ├── UseCase
│         │      └── HogehogeUseCase.php
│         └── Service
│               └── FugafugaService.php
├── Model
│   ├── Behavior
│   ├── Entity
│   └── Table
├── Template
├── View
│  ...

テストがしづらい(問題度:大)

コンポーネントのテストが絶望的に面倒くさいです。
ComponentがComponentを使用できるようにしている関係上、コンポーネント同士が密結合な状態となっております。

このため例えばモックとの相性が悪いです。
テスト対象のComponentが複数のComponentに依存している場合、
複数のComponentをモッキングすることになりますが、
これがどうにもきれいな形に収めることができませんでした。

問題点対処

そもそもComponentを使用しないように方針転換

$component変数(Service Locater)とテストの相性がそもそも悪いのではないか?
→テストしやすい形を考え、DIの形にする必要があると判断
→構成を1から見直し、CakePHP的な構造を捨て、Componentディレクトリを極力使わないよう方針転換。

改変後の構成

Controllerと同階層にUseCaseやServiceディレクトリを作成し、
Controllerがnewで呼び出し。
(さらに言えばControllerとUseCaseの間にFormクラスを使用するとよい。今回は割愛。 ex:モデルのないフォーム)

src
├── Controller
│   └── ExampleController.php
├── Model
│   ├── Behavior
│   ├── Entity
│   └── Table
├── Service
│   └── FugafugaService.php
├── Template
├── UseCase
│   └── HogehogeUseCase.php
├── View
│  ...
ExampleController.php
class ExampleController extends AppController
{
    public function index()
    {
        $service = new FugafugaService();
        return (new HogehogeUseCase($service))->action($this->request->getData());
    }
}
HogehogeUseCase.php
class HogehogeUseCase
{
    /** @var FugafugaService $service */
    private $service;

    public function __construct(FugafugaService $service)
    {
        $this->service = $service;
    }

    public function action(array $request)
    {
        return $this->service->get($request);
    }
}

結果

この例では特に処理が無いため実感が湧きづらいですが、
UseCase層以下のテストがしやすくなりました。
またついでに、UseCaseやServiceがController下にあるという状態も解消されました。

さいごに

伝えたかった事

Componentの肥大化を抑え、またテストのしやすい形にできたのでとりあえず満足しています。
同じ道を通る人も多いかと思いましたので、同じ轍を踏む前にこの失敗をご参考にしていただければ幸いです。

意見募集

このディレクトリ構成はCakePHPの構造を破壊したような感じがしてどうにも煮え切らない気分ではあります。

  • こうやった方がいいんじゃない?
  • こうやったらスッキリしたよ。

といった意見がありましたら、アドバイスいただければ幸いです。

参考

CakePHP3でComponentのフォルダ階層を変更する

3
3
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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?