フレームワークへの依存度を下げるFLUDパターン (4.5)

フレームワークへの依存度を下げるFLUDパターン (4) の続きです。

CodeIgniter4のチュートリアルのコードをFLUDパターンに変更しています。

重複したコードが見られますので、FLUDパターンとは直接関係ないですが、リファクタリングしておきます。

リファクタリング

NewsとNewsDto

NewsNewsDto に重複したコードがありますので、トレイト化します。

--- a/packages/news/src/Domain/News/News.php
+++ b/packages/news/src/Domain/News/News.php
@@ -4,10 +4,12 @@ declare(strict_types=1);

 namespace Acme\News\Domain\News;

-use LogicException;
+use Acme\Shared\GetterTrait;

 class News
 {
+    use GetterTrait;
+
     private $id;
     private $title;
     private $slug;
@@ -24,22 +26,4 @@ class News
         $this->slug = $slug;
         $this->body = $body;
     }
-
-    public function __isset(string $name)
-    {
-        if (! property_exists($this, $name)) {
-            throw new LogicException('No such property: ' . $name);
-        }
-
-        return isset($this->$name);
-    }
-
-    public function __get(string $name)
-    {
-        if (! property_exists($this, $name)) {
-            throw new LogicException('No such property: ' . $name);
-        }
-
-        return $this->$name;
-    }
 }
--- a/packages/news/src/UseCase/News/NewsDto.php
+++ b/packages/news/src/UseCase/News/NewsDto.php
@@ -4,10 +4,12 @@ declare(strict_types=1);

 namespace Acme\News\UseCase\News;

-use LogicException;
+use Acme\Shared\GetterTrait;

 class NewsDto
 {
+    use GetterTrait;
+
     private $id;
     private $title;
     private $slug;
@@ -24,22 +26,4 @@ class NewsDto
         $this->slug = $slug;
         $this->body = $body;
     }
-
-    public function __isset(string $name)
-    {
-        if (! property_exists($this, $name)) {
-            throw new LogicException('No such property: ' . $name);
-        }
-
-        return isset($this->$name);
-    }
-
-    public function __get(string $name)
-    {
-        if (! property_exists($this, $name)) {
-            throw new LogicException('No such property: ' . $name);
-        }
-
-        return $this->$name;
-    }
 }

GetterTrait は複数のパッケージで使いそうですので、Sharedパッケージに配置します。

packages/shared/src/GetterTrait.php

<?php

declare(strict_types=1);

namespace Acme\Shared;

use LogicException;

trait GetterTrait
{
    public function __isset(string $name)
    {
        if (! property_exists($this, $name)) {
            throw new LogicException('No such property: ' . $name);
        }

        return isset($this->$name);
    }

    public function __get(string $name)
    {
        if (! property_exists($this, $name)) {
            throw new LogicException('No such property: ' . $name);
        }

        return $this->$name;
    }
}

UseCase

ユースケースクラスにも重複がありますので、抽象クラスを抽出します。

--- a/packages/news/src/UseCase/News/CreateNewsUseCase.php
+++ b/packages/news/src/UseCase/News/CreateNewsUseCase.php
@@ -5,20 +5,9 @@ declare(strict_types=1);
 namespace Acme\News\UseCase\News;

 use Acme\News\Domain\News\News;
-use Acme\News\Domain\News\NewsRepositoryInterface;

-class CreateNewsUseCase
+class CreateNewsUseCase extends AbstractNewsUserCase
 {
-    /**
-     * @var NewsRepositoryInterface
-     */
-    private $newsRepository;
-
-    public function __construct(NewsRepositoryInterface $newsRepositry)
-    {
-        $this->newsRepository = $newsRepositry;
-    }
-
     public function run(string $title, string $slug, string $body): void
     {
         $news = new News(
--- a/packages/news/src/UseCase/News/GetNewsItemUseCase.php
+++ b/packages/news/src/UseCase/News/GetNewsItemUseCase.php
@@ -5,20 +5,9 @@ declare(strict_types=1);
 namespace Acme\News\UseCase\News;

 use Acme\News\Domain\News\News;
-use Acme\News\Domain\News\NewsRepositoryInterface;

-class GetNewsItemUseCase
+class GetNewsItemUseCase extends AbstractNewsUserCase
 {
-    /**
-     * @var NewsRepositoryInterface
-     */
-    private $newsRepository;
-
-    public function __construct(NewsRepositoryInterface $newsRepositry)
-    {
-        $this->newsRepository = $newsRepositry;
-    }
-
     public function run(string $slug): ?NewsDto
     {
         /** @var News|null $news */
--- a/packages/news/src/UseCase/News/GetNewsListUseCase.php
+++ b/packages/news/src/UseCase/News/GetNewsListUseCase.php
@@ -5,20 +5,9 @@ declare(strict_types=1);
 namespace Acme\News\UseCase\News;

 use Acme\News\Domain\News\News;
-use Acme\News\Domain\News\NewsRepositoryInterface;

-class GetNewsListUseCase
+class GetNewsListUseCase extends AbstractNewsUserCase
 {
-    /**
-     * @var NewsRepositoryInterface
-     */
-    private $newsRepository;
-
-    public function __construct(NewsRepositoryInterface $newsRepositry)
-    {
-        $this->newsRepository = $newsRepositry;
-    }
-
     /**
      * @return NewsDto[]
      */

packages/news/src/UseCase/News/AbstractNewsUserCase.php

<?php

declare(strict_types=1);

namespace Acme\News\UseCase\News;

use Acme\News\Domain\News\NewsRepositoryInterface;

abstract class AbstractNewsUserCase
{
    /**
     * @var NewsRepositoryInterface
     */
    protected $newsRepository;

    public function __construct(NewsRepositoryInterface $newsRepositry)
    {
        $this->newsRepository = $newsRepositry;
    }
}

Newsコントローラ

重複したコードをコンストラクタに抽出します。

--- a/app/Controllers/News.php
+++ b/app/Controllers/News.php
@@ -10,10 +10,19 @@ use Acme\News\UseCase\News\CreateNewsUseCase;

 class News extends BaseController
 {
+    /**
+     * @var NewsRepository
+     */
+    private $repository;
+
+    public function __construct()
+    {
+        $this->repository = model(NewsRepository::class);
+    }
+
     public function index()
     {
-        $repository = model(NewsRepository::class);
-        $useCase = new GetNewsListUseCase($repository);
+        $useCase = new GetNewsListUseCase($this->repository);

         /** @var NewsDto[] $newsList */
         $newsList = $useCase->run();
@@ -30,8 +39,7 @@ class News extends BaseController

     public function view($slug = null)
     {
-        $repository = model(NewsRepository::class);
-        $useCase = new GetNewsItemUseCase($repository);
+        $useCase = new GetNewsItemUseCase($this->repository);

         $data['news'] = $useCase->run($slug);

@@ -48,8 +56,7 @@ class News extends BaseController

     public function create()
     {
-        $repository = model(NewsRepository::class);
-        $useCase = new CreateNewsUseCase($repository);
+        $useCase = new CreateNewsUseCase($this->repository);

         if ($this->request->getMethod() === 'post' && $this->validate([
             'title' => 'required|min_length[3]|max_length[255]',

ディレクトリ構成

現状のディレクトリ構成は以下になります。

app/
├── Config
│   ├── Autoload.php
├── Controllers
│   ├── News.php
├── Models
│   └── NewsRepository.php
└── Views
     └── news
          ├── create.php
          ├── overview.php
          ├── success.php
          └── view.php

packages/
├── news ... Newsパッケージ
│   └── src
│       ├── Domain ... ドメイン層
│       │   └── News
│       │       ├── News.php
│       │       └── NewsRepositoryInterface.php
│       └── UseCase ... ユースケース層
│           └── News
│               ├── AbstractNewsUserCase.php ← 追加
│               ├── CreateNewsUseCase.php
│               ├── GetNewsItemUseCase.php
│               ├── GetNewsListUseCase.php
│               └── NewsDto.php
└── shared ... Sharedパッケージ
    └── src
        └── GetterTrait.php ← 追加

app/ 以下のファイルはモデルのファイル名を変更しただけで配置はそのままにしています。

packages/ 以下にドメイン層とユースケース層のコードが追加されています。

フレームワークへの依存度を下げるFLUDパターン (5) へ続く。

参考

Date: 2021/12/23

Tags: codeigniter, codeigniter4, ddd, mvc, php