DTOのススメ

# 概要

業務で以下のようなコードをよく見かけます。

- DBから取得したデータを配列でViewに渡す。
- Viewで色々制御している。

```php
class ProductController
{
  public function idAction($id): void {
    $this->products = $this->get('product_model')->fetchProductById($id);
  }
}

class ProductModel
{
  public function fetchProductById($id): array {
    // DBからデータを配列で取得する
    return $qb->getArrayResult();
  }
}

// View
<ul>
  <?php foreach ($this->products as $product): ?>
    <li>
      <?= escape($product['name']) ?><br />
      // 税込みであれば税額を足す
      <?php if ($product['in_tax'] === true): ?>
        <?= escape($product['price'] + $product['tax']) ?><br />
      <?php else: ?>
        <?= escape($product['price']) ?><br />
      <?php endif; ?>
      // サムネイルが複数設定されていればループ
      <?php if (is_array($product['thumbnail'])): ?>
        <?php foreach ($product['thumbnail']) as $thumbnail): ?>
          <img src="<?= escape($thumbnail) ?>" /><br />
        <?php endforeach; ?>
      <?php else: ?>
        <img src="<?= escape($product['thumbnail']) ?>" />
      <?php endif; ?>
    </li>
  <?php endforeach; ?>
</ul>
```

上記のコードには、いくつかのデメリットがあります。

- Viewが複雑になりがちで、必須のパラメータなどのデータの制約が読み取りづらい。
- PHPの配列はどんな値でも入ってしまうため、プログラムが壊れやすい。
- 配列のキーをIDEが補完してくれないため、毎回キー名を確認しないといけない。
- 一般的にViewはテストしにくいため、テストの負担が増える。

これらのデメリットは、DTOと呼ばれるクラスを作成することで改善できます。
DTOについては、以下の記事の解説がわかりやすいので、ご参照ください。
https://qiita.com/sagaraya/items/96708cd451021fb040b7

# DTOを使って書き換えてみる

先ほどのコードをDTOを使ったパターンに書き換えてみます。

```php
class ProductController
{
  public function idAction($id): void {
    $this->products = $this->get('product_model')->fetchProductById($id);
  }
}

class ProductModel
{
  public function fetchProductById($id): Product[] {
    // DBからデータを配列で取得する
    $result = $qb->getArrayResult();
    $products = [];
    foreach ($result as $row) {
      // DTOを生成して返す
      $product = new Product();
      $product->setName($row['name']);
      $product->setPrice($row['price']);
      ...
      $products[] = $product;
    }
    return $products;
  }
}

// これがDTO
class Product
{
  private $name;
  private $price;
  ...
  public function setName($name) {
    $this->name = $name;
  }
  public function setPrice($price) {
    $this->price = $price;
  }
  ...
  public function getName(): string {
    return $this->name;
  }
  public function getPrice(): int {
    $price = $this->price;
    if ($this->in_tax) {
      $price += $this->tax;
    }
    return $price;
  }
  public function getThumbnails(): string[] {
    if (!is_array($this->thumbnail)) {
      return [$this->thumbnail];
    }
    return $this->thumbnail;
  }
  ...
}

// View
<ul>
  <?php foreach ($this->products as $product): ?>
    <li>
      <?= escape($product->getName()) ?><br />
      // Viewでの制御が不要になる。
      <?= escape($product->getPrice()) ?><br />
      // サムネイルは必ず配列になるので、安心してループできる。
      <?php foreach ($product->getThumbnails() as $thumbnail): ?>
        <img src="<?= escape($thumbnail) ?>" /><br />
      <?php endforeach; ?>
    </li>
  <?php endforeach; ?>
</ul>
```

# まとめ

DTOを使うことにより、以下のようなメリットが生まれました。

- Viewからifが消え読みやすいコードに。データの制約もDTOに集約されるため読みやすい。
- DTOクラスが型を担保してくれるので、プログラムが壊れにくい。
- IDEがgetterやsetterを補完してくれるため、スピーディにコーディングできる。
- DTOはただのクラスなのでテストしやすい。

参考にしていただければと思います。