MVCモデルのViewにDTOを導入するとどうなるか

## はじめに

株式会社じげんのアレクセイです
プロパンガス会社の情報・見積もり比較サービス　エネピの開発を担当しています。

今回は、Viewでの作業を楽にするためにDTOの導入について書いていきます。
コントローラーで取得したモデルをそのままViewへ渡すことが多いと思いますが、Viewでモデルのリレーションを呼び出したり、モデルのプロパティによって分岐処理を入れたりします。分岐が複雑になり、同じ分岐が増え、だんだんViewのソースが読みづらくなっていきます。ビズネスロジックが変わったら何箇所を直さないといけないことになります。定期的にこういう依頼が来るとViewでの作業が大変です。そしてリファクタリングの時にモデルのリレーションやプロパティをなくすか変更する場合はエラーが起こりやすくなります。

## 始める前に

この記事のソースコードは実際に使われているものではないので、以下の画像をHTMLページとしてメージしています。
![Screen Shot 2021-11-30 at 18.17.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286738/8c4a8d74-8b05-fc16-b76a-58ed9009101d.png)

## DTOとは？

【Data Transfer Object】オブジェクト指向プログラミングでよく用いられる典型的なオブジェクトの設計パターン（デザインパターン）の一つで、関連するデータを一つにまとめ、データの格納・読み出しのためのメソッドを定義したオブジェクトのことです。異なるプログラム間やコンピュータ間でひとまとまりのデータを受け渡す際に用いられます。
引用：https://e-words.jp/w/DTO.html

## DTOを導入する前のView

```php:view.html
@if ($book->author)
  @if ($book->author->firstName && $book->author->lastName)
  <p>{{ $book->author->firstName }} {{ $book->author->lastName }}</p>
  @else
    <p>Unknown Author</p>
  @endif
<p>{{ $book->author->registeredDate->format('Y-m-d') }}</p>
@endif

<p>Price: {{ $book->price ? number_format().'円' : 'Sold out' }}</p>
<a href="/buy-me">{{ session('ab.test') ? 'Buy now' : 'Buy' }}</a>

@if ($book->images)
  @foreach ($book->images as $image)
  <div><img src="{{ $image->url }}"></div>
  @endforeach
@elseif
  <div><img src="no-image.jpg"></div>
@endif
```

## DTOオブジェクトを用意する

```php:dto.php

class BookShowDto extends AbstractDto
{
    /**
     * @var bool
     */
    public $showAuthorSection;

    /**
     * @var string
     */
    public $authorFullName;

    /**
     * @var mixed|string|null
     */
    public $authorRegisteredDate;

    /**
     * @var array
     */
    public $bookImages = [];

    /**
     * @var string
     */
    public $bookPrice;

    /**
     * @var string
     */
    public $buyButtonText = 'Buy'; // or HTML
}
```

## DTOオブジェクトのプロパティを設定する

```php:logic.php
$dto = new BookShowDto;

if ($book->author) {
  $dto->showAuthorSection = true;
  $dto->authorFullName = $book->author->firstName.$book->author->lastName;

  if (!$dto->authorFullName) {
    $dto->authorFullName = 'Unknown Author';
  }
  $dto->authorRegisteredDate = $book->author->registeredDate->format('Y-m-d');
}

$dto->bookPrice ? $book->price ? number_format().'円' : 'Sold out';

@foreach ($book->images as $image) {
  $dto->bookImages[] = $image->url;
}

// Default image
empty($dto->bookImages) and $dto->bookImages[] = '/images/no-image.jpg';

// AB testing
session('ab.test') and $dto->buyButtonText = 'Buy now';

```

## DTOを導入する後のView

```php:view.html
@if ($dto->showAuthorSection)
  <p>{{ $dto->authorFullName }}</p>
  <p>{{ $dto->authorRegisteredDate }}</p>
@endif

<p>Price: {{ $dto->bookPrice }}</p>
<a href="/buy-me">{{ $dto->buyButtonText }}</a>

@foreach ($dto->bookImages as $url)
<div><img src="{{ $url }}"></div>
@endforeach
```

## まとめ

単純なページであればメリットをあまり感じませんが、リレーションが多い、大量な分岐が入っているページならViewとビジネスロジックの間にDTOを置けば作業が楽になるかと思います。実際のプロジェクトに導入したら以下のメリットを感じました。
① Viewが読みやすい。
② 多くの分岐が１箇所にまとまっている、HTMLと混ざっていないので、HTMLだけでなくphpのロジックも読みやすい。
③ DTOのデフォルト値を設定できるので、リファクタリングの際に値の設定を忘れたり、リレーションが変わる際にもエラーが起きない。
④ ビジネスロジックが重かったらDTOをそのまま簡単にキャッシュに乗せられる。
⑤ ボタンや文言などのABテストの実装がやりやすい。

みなさんも是非試してみてください。