【Laravel】DTO(Data Transfer Object)クラスを使った例の紹介

2024年7月6日Laravel,PHP

DTO(Data Transfer Object)とは?

「Data Transfer Object」(DTO)は、データの受け渡しや共有を行うためのオブジェクトを表すデザインパターンです。
主に異なるコンポーネントや層間でデータを転送するために使用されます。
※データベースからのデータ取得や外部サービスとのやり取りなど、データを異なるコンテキストで受け渡す必要がある場面で特に役立ちます。

DTOクラスの書き方

DTOクラスは、特定のデータ構造を表すためのクラスを作成します。
DTOクラスは、プロパティ(データ)とそれにアクセスするための以下のメソッドが一般的と言われています。

  • ゲッター
  • セッターメソッド

スポンサードサーチ

DTOクラス使う理由

外部からプロパティにアクセスする際にカプセル化を保ちながらアクセスができます。
VSCodeなどのエディタでジャンプできなかったところでも、DTOクラスを自体を返しているので値にジャンプすることができます。

DTOを使ったクラスの定義

namespace App\Dto;

/**
 * ユーザーのデータ転送オブジェクト (DTO)
 * 
 * ユーザーデータをオブジェクトとしてやり取りするためのクラスです。
 */
class UserDto
{
    private int $id;
    private string $name;
    private string $email;
    private string $createdAt;
    private string $updatedAt;

    /**
     * コンストラクタ
     *
     * @param int $id
     * @param string $name
     * @param string $email
     * @param string $createdAt
     * @param string $updatedAt
     */
    public function __construct(int $id, string $name, string $email, string $createdAt, string $updatedAt)
    {
        $this->id = $id;
        $this->name = $name;
        $this->email = $email;
        $this->createdAt = $createdAt;
        $this->updatedAt = $updatedAt;
    }

    // ユーザーIDを取得する
    public function getId(): int
    {
        return $this->id;
    }

    // ユーザー名を取得する
    public function getName(): string
    {
        return $this->name;
    }

    // ユーザーのメールアドレスを取得する
    public function getEmail(): string
    {
        return $this->email;
    }

    // ユーザー作成日時を取得する
    public function getCreatedAt(): string
    {
        return $this->createdAt;
    }

    // ユーザー更新日時を取得する
    public function getUpdatedAt(): string
    {
        return $this->updatedAt;
    }

    /**
     * 配列からUserDtoオブジェクトを生成する
     *
     * @param array $data
     * @return UserDto
     */
    public static function fromArray(array $data): self
    {
        return new self(
            $data['id'],
            $data['name'],
            $data['email'],
            $data['created_at'],
            $data['updated_at']
        );
    }

    /**
     * UserDtoオブジェクトを配列に変換する
     *
     * @return array
     */
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->createdAt,
            'updated_at' => $this->updatedAt
        ];
    }

    /**
     * JSON文字列からUserDtoオブジェクトを生成する
     *
     * @param string $json
     * @return UserDto
     */
    public static function fromJson(string $json): self
    {
        $data = json_decode($json, true);
        return self::fromArray($data);
    }

    /**
     * UserDtoオブジェクトをJSON文字列に変換する
     *
     * @return string
     */
    public function toJson(): string
    {
        return json_encode($this->toArray());
    }

    /**
     * オブジェクト(stdClassなど)からUserDtoオブジェクトを生成する
     *
     * @param object $object
     * @return UserDto
     */
    public static function fromObject(object $object): self
    {
        return new self(
            $object->id,
            $object->name,
            $object->email,
            $object->created_at,
            $object->updated_at
        );
    }

    /**
     * UserDtoオブジェクトをオブジェクト(stdClassなど)に変換する
     *
     * @return object
     */
    public function toObject(): object
    {
        return (object) $this->toArray();
    }

    /**
     * データベースのレコード(行)からUserDtoオブジェクトを生成する
     *
     * @param \Illuminate\Database\Eloquent\Model $record
     * @return UserDto
     */
    public static function fromDatabaseRecord(\Illuminate\Database\Eloquent\Model $record): self
    {
        return new self(
            $record->id,
            $record->name,
            $record->email,
            $record->created_at->toDateTimeString(),
            $record->updated_at->toDateTimeString()
        );
    }

    /**
     * UserDtoオブジェクトをデータベースのレコード(行)形式に変換する
     *
     * @return array
     */
    public function toDatabaseRecord(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->createdAt,
            'updated_at' => $this->updatedAt
        ];
    }
}

スポンサードサーチ

DTOクラスでよく使うメソッド

DTOクラスでよく使うメソッドです

配列

  • fromArray:
    • 配列からDTOオブジェクトを生成する
  • toArray:
    • DTOオブジェクトを配列に変換する

fromArray

fromArrayは配列からUserDtoオブジェクトを生成します。

   /**
     * 配列からUserDtoオブジェクトを生成する
     *
     * @param array $data
     * @return UserDto
     */
    public static function fromArray(array $data): self
    {
        return new self(
            $data['id'],
            $data['name'],
            $data['email'],
            $data['created_at'],
            $data['updated_at']
        );
    }

toArray

toArrayはUserDtoオブジェクトを配列に変換します。

/**
     * UserDtoオブジェクトを配列に変換する
     *
     * @return array
     */
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->createdAt,
            'updated_at' => $this->updatedAt
        ];
    }

JSON

  • fromJson:
    • JSON文字列からDTOオブジェクトを生成する
  • toJson:
    • DTOオブジェクトをJSON文字列に変換する

fromJson

fromJsonはJSON文字列からUserDtoオブジェクトを生成します。

    /**
     * JSON文字列からUserDtoオブジェクトを生成する
     *
     * @param string $json
     * @return UserDto
     */
    public static function fromJson(string $json): self
    {
        $data = json_decode($json, true);
        return self::fromArray($data);
    }

toJson

toJsonはUserDtoオブジェクトをJSON文字列に変換します。

    /**
     * UserDtoオブジェクトをJSON文字列に変換する
     *
     * @return string
     */
    public function toJson(): string
    {
        return json_encode($this->toArray());
    }

DTO

  • fromObject:
    • オブジェクト(stdClassなど)からDTOオブジェクトを生成する
  • toObject:
    • DTOオブジェクトをオブジェクト(stdClassなど)に変換する

スポンサードサーチ

fromObject

fromObjectはオブジェクト(stdClassなど)からUserDtoオブジェクトを生成します。

    /**
     * オブジェクト(stdClassなど)からUserDtoオブジェクトを生成する
     *
     * @param object $object
     * @return UserDto
     */
    public static function fromObject(object $object): self
    {
        return new self(
            $object->id,
            $object->name,
            $object->email,
            $object->created_at,
            $object->updated_at
        );
    }

toObject

toObjectはUserDtoオブジェクトをオブジェクト(stdClassなど)に変換します。

    /**
     * UserDtoオブジェクトをオブジェクト(stdClassなど)に変換する
     *
     * @return object
     */
    public function toObject(): object
    {
        return (object) $this->toArray();
    }

データベース

  • fromDatabaseRecord:
    • データベースのレコード(行)からDTOオブジェクトを生成する
  • toDatabaseRecord:
    • DTOオブジェクトをデータベースのレコード(行)形式に変換する

fromDatabaseRecord:

データベースのレコード(行)から UserDtoオブジェクトを生成します。

    /**
     * データベースのレコード(行)からUserDtoオブジェクトを生成する
     *
     * @param \Illuminate\Database\Eloquent\Model $record
     * @return UserDto
     */
    public static function fromDatabaseRecord(\Illuminate\Database\Eloquent\Model $record): self
    {
        return new self(
            $record->id,
            $record->name,
            $record->email,
            $record->created_at->toDateTimeString(),
            $record->updated_at->toDateTimeString()
        );
    }

toDatabaseRecord

UserDtoオブジェクトをデータベースのレコード(行)形式に変換する

    /**
     * UserDtoオブジェクトをデータベースのレコード(行)形式に変換する
     *
     * @return array
     */
    public function toDatabaseRecord(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->createdAt,
            'updated_at' => $this->updatedAt
        ];
    }

new selfとnew DTOクラスの違い

new self

selfは現在のクラスを指します。
selfを使うと、静的なコンテキストで呼び出される際に、そのクラスの現在のインスタンスを返します。
クラスが継承された場合、selfはその継承先のクラスのインスタンスを指します。

  • 意味: 現在のクラスのインスタンスを作成します。
  • 特徴: クラスが継承された場合、継承先のクラスのインスタンスを作成します。
  • 使用場面: ファクトリメソッドやクローンメソッドなどで、現在のクラスまたはそのサブクラスのインスタンスを生成する際に使用します。

new DTOクラス

new DTOクラスは特定のクラス名を指します。
明示的にDTOクラスのインスタンスを生成するため、継承されたクラスから呼び出されても、常に new DTOクラスのインスタンスを生成します。

  • 意味: 明示的に UserDto クラスのインスタンスを作成します。
  • 特徴: クラスが継承された場合でも、必ず UserDto クラスのインスタンスを作成します。
  • 使用場面: 具体的なクラスのインスタンスを作成したい場合に使用します。

DTOを使った例

今回の例では以下のクラスを使います。

こちらがtreeです

app
├── Dto
│   └── UserDto.php
├── Http
│   └── Controllers
│       └── UserController.php
├── Models
│   └── User.php
├── Repositories
│   └── UserRepository.php
├── Services
│   └── UserService.php
└── Usecases
    ├── UserUsecase.php

使用したクラス

使用したクラスを一覧で紹介です

  • Controller
  • Usecase interface
  • Service
  • Repository
  • Model
  • DTO

各ファイルの役割

app/Dto/UserDto.php

  • ユーザーのデータ転送オブジェクト (DTO)。データの転送を簡潔に行うためのオブジェクト。

app/Http/Controllers/UserController.php

  • HTTPリクエストを受け取り、適切なサービスやユースケースを呼び出すコントローラー。

app/Models/User.php

  • データベースの users テーブルと対応するEloquentモデル。

app/Repositories/UserRepository.php

  • データベースとのやり取りを抽象化するリポジトリクラス。データ取得、作成、更新、削除などの操作を実装。

app/Services/UserService.php

  • ビジネスロジックを実装するサービスクラス。リポジトリを利用してデータ操作を行う。

app/Usecases/UserUsecase.php

  • ユーザーに関するユースケースを定義するインターフェース。サービスクラスがこれを実装して具体的なビジネスロジックを分離。

コントローラー

app/Http/Controllers/UserController

icon

ControllerではHTTPリクエストを受け取り、Usecaseインターフェースを実装した、サービスのメソッドを呼び出します。

  • Usecaseインターフェースを実装:UserService(UserServiceクラスのimplementsがUserUsecase)
  • サービスのメソッド:execte
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Usecases\UserUsecase;
use App\Dto\UserDto;

class UserController extends Controller
{
    private UserUsecase $userUsecase;

    public function __construct(UserUsecase $userUsecase)
    {
        $this->userUsecase = $userUsecase;
    }

    /**
     * ユーザーをIDで取得する
     *
     * @param int $id
     * @return \Illuminate\Http\JsonResponse
     */
    public function getUserById(int $id)
    {
        try {
            $userDto = $this->userUsecase->execute($id);
            return response()->json($userDto->toArray());
        } catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 404);
        }
    }

    /**
     * 新しいユーザーを作成する
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function createUser(Request $request)
    {
        $data = $request->only(['name', 'email']);
        $input = ['action' => 'create', 'data' => $data];
        
        try {
            $userDto = $this->userUsecase->execute($input);
            return response()->json($userDto->toArray(), 201);
        } catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }

    /**
     * ユーザー情報を更新する
     *
     * @param int $id
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function updateUser(int $id, Request $request)
    {
        $data = $request->only(['name', 'email']);
        $input = ['action' => 'update', 'id' => $id, 'data' => $data];

        try {
            $userDto = $this->userUsecase->execute($input);
            return response()->json($userDto->toArray());
        } catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }

    /**
     * ユーザーを削除する
     *
     * @param int $id
     * @return \Illuminate\Http\JsonResponse
     */
    public function deleteUser(int $id)
    {
        $input = ['action' => 'delete', 'id' => $id];

        try {
            $this->userUsecase->execute($input);
            return response()->json(null, 204);
        } catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }
}

ユースケースインターフェース

app/Usecases/UserUsecase

ユーザーに関するユースケースを定義するインターフェースです。

icon

executeメソッドが定義され、サービスクラスがこれを実装しています。

namespace App\Usecases;

use App\Dto\UserDto;

/**
 * ユーザー関連のユースケースインターフェース
 * 
 * ユーザーに関するユースケースを定義するインターフェースです。
 * サービスクラスが実装することで、具体的なビジネスロジックを分離します。
 */
interface UserUsecase
{
    /**
     * ユースケースを実行する
     *
     * @param mixed $input
     * @return UserDto|null
     */
    public function execute($input): ?UserDto;
}

サービスクラス

app/Services/UserService

ユーザー関連のビジネスロジックを実装するクラスです。

ControllerではHTTPリクエストを受け取り、Usecaseインターフェースを実装したサービスのメソッドを呼び出します。

implementsでUsecaseを使用しています。
Usecaseの処理でRepositoryを使用して、DTOでデータ転送を行なっています。

namespace App\Services;

use App\Dto\UserDto;
use App\Repositories\UserRepository;
use App\Usecases\UserUsecase;

/**
 * ユーザー関連のサービスクラス
 * 
 * ビジネスロジックを実装するクラスです。
 * リポジトリクラスを利用してデータ操作を行います。
 */
class UserService implements UserUsecase
{
    private UserRepository $userRepository;

    /**
     * コンストラクタ
     *
     * @param UserRepository $userRepository
     */
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    /**
     * ユースケースを実行する
     *
     * @param mixed $input
     * @return UserDto|null
     */
    public function execute($input): ?UserDto
    {
        if (is_int($input)) {
            // IDでユーザーを取得
            $userData = $this->userRepository->findById($input);
            return UserDto::fromArray($userData);
        } elseif (is_array($input) && isset($input['action'])) {
            switch ($input['action']) {
                case 'create':
                    // 新しいユーザーを作成
                    $userId = $this->userRepository->create($input['data']);
                    $userData = $this->userRepository->findById($userId);
                    return UserDto::fromArray($userData);
                case 'update':
                    // ユーザー情報を更新
                    $this->userRepository->update($input['id'], $input['data']);
                    $userData = $this->userRepository->findById($input['id']);
                    return UserDto::fromArray($userData);
                case 'delete':
                    // ユーザーを削除
                    $this->userRepository->delete($input['id']);
                    return null;
                default:
                    throw new \InvalidArgumentException('Invalid action');
            }
        } else {
            throw new \InvalidArgumentException('Invalid input');
        }
    }
}

リポジトリクラス

app/Repositories/UserRepository

ユーザーに関するデータベース操作を行うクラスです。
サービスから呼び出され、データベース操作を実行します。

Eloquentモデルを使用してデータベースとやり取りとりを行います。

namespace App\Repositories;

use App\Models\User;

/**
 * ユーザー関連のリポジトリクラス
 */
class UserRepository
{
    /**
     * IDでユーザーを検索する
     *
     * @param int $id
     * @return array
     */
    public function findById(int $id): array
    {
        $user = User::findOrFail($id);
        return $user->toArray();
    }

    /**
     * 新しいユーザーを作成する
     *
     * @param array $data
     * @return int 新しいユーザーID
     */
    public function create(array $data): int
    {
        $user = User::create($data);
        return $user->id;
    }

    /**
     * ユーザー情報を更新する
     *
     * @param int $id
     * @param array $data
     */
    public function update(int $id, array $data): void
    {
        $user = User::findOrFail($id);
        $user->update($data);
    }

    /**
     * ユーザーを削除する
     *
     * @param int $id
     */
    public function delete(int $id): void
    {
        $user = User::findOrFail($id);
        $user->delete();
    }
}

モデル

app/Models/User

データベースのテーブルと対応するEloquentモデルです。

リポジトリから呼び出され、データベースの操作を行なっています。

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

/**
 * ユーザーモデルクラス
 */
class User extends Model
{
    protected $table = 'users';

    // 変更可能なカラムを定義
    protected $fillable = [
        'name',
        'email',
    ];

    // タイムスタンプを自動管理する場合は true
    public $timestamps = true;
}

DTOクラス

app/Dto/UserDto

データ転送オブジェクト (DTO)クラスです。

icon

リポジトリから取得したデータを元にDTOを生成し、サービスがそれを返却しています

namespace App\Dto;

/**
 * ユーザーのデータ転送オブジェクト (DTO)
 * 
 * ユーザーデータをオブジェクトとしてやり取りするためのクラスです。
 */
class UserDto
{
    private int $id;
    private string $name;
    private string $email;
    private string $createdAt;
    private string $updatedAt;

    /**
     * コンストラクタ
     *
     * @param int $id
     * @param string $name
     * @param string $email
     * @param string $createdAt
     * @param string $updatedAt
     */
    public function __construct(int $id, string $name, string $email, string $createdAt, string $updatedAt)
    {
        $this->id = $id;
        $this->name = $name;
        $this->email = $email;
        $this->createdAt = $createdAt;
        $this->updatedAt = $updatedAt;
    }

    // ユーザーIDを取得する
    public function getId(): int
    {
        return $this->id;
    }

    // ユーザー名を取得する
    public function getName(): string
    {
        return $this->name;
    }

    // ユーザーのメールアドレスを取得する
    public function getEmail(): string
    {
        return $this->email;
    }

    // ユーザー作成日時を取得する
    public function getCreatedAt(): string
    {
        return $this->createdAt;
    }

    // ユーザー更新日時を取得する
    public function getUpdatedAt(): string
    {
        return $this->updatedAt;
    }

    /**
     * 配列からUserDtoオブジェクトを生成する
     *
     * @param array $data
     * @return UserDto
     */
    public static function fromArray(array $data): self
    {
        return new self(
            $data['id'],
            $data['name'],
            $data['email'],
            $data['created_at'],
            $data['updated_at']
        );
    }

    /**
     * UserDtoオブジェクトを配列に変換する
     *
     * @return array
     */
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->createdAt,
            'updated_at' => $this->updatedAt
        ];
    }

    /**
     * JSON文字列からUserDtoオブジェクトを生成する
     *
     * @param string $json
     * @return UserDto
     */
    public static function fromJson(string $json): self
    {
        $data = json_decode($json, true);
        return self::fromArray($data);
    }

    /**
     * UserDtoオブジェクトをJSON文字列に変換する
     *
     * @return string
     */
    public function toJson(): string
    {
        return json_encode($this->toArray());
    }

    /**
     * オブジェクト(stdClassなど)からUserDtoオブジェクトを生成する
     *
     * @param object $object
     * @return UserDto
     */
    public static function fromObject(object $object): self
    {
        return new self(
            $object->id,
            $object->name,
            $object->email,
            $object->created_at,
            $object->updated_at
        );
    }

    /**
     * UserDtoオブジェクトをオブジェクト(stdClassなど)に変換する
     *
     * @return object
     */
    public function toObject(): object
    {
        return (object) $this->toArray();
    }

    /**
     * データベースのレコード(行)からUserDtoオブジェクトを生成する
     *
     * @param \Illuminate\Database\Eloquent\Model $record
     * @return UserDto
     */
    public static function fromDatabaseRecord(\Illuminate\Database\Eloquent\Model $record): self
    {
        return new self(
            $record->id,
            $record->name,
            $record->email,
            $record->created_at->toDateTimeString(),
            $record->updated_at->toDateTimeString()
        );
    }

    /**
     * UserDtoオブジェクトをデータベースのレコード(行)形式に変換する
     *
     * @return array
     */
    public function toDatabaseRecord(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->createdAt,
            'updated_at' => $this->updatedAt
        ];
    }
}

実装やエラーが解決できない場合

プログラミングの実装やエラーでどうしてもわからない場合はメンターに相談するのが一番です。

考えている、見えている範囲が狭くなり、解決から遠くに行って何時間も、何日も経っていることなんてよくある話です。

そういう時は聞ける先輩や、メンターに相談することが大事です。

僕にも相談可能なので気軽に相談してください。

Twitterからの連絡だと確実ですよ。

オンラインスクールやプログラミングスクールといったプログラミングを学べる方法もあるので、そちらもぜひ活用してもいいと思います。

Web開発で分からない時

オンライン完結型スクール DMM WEBCAMP PRO

アプリ開発で分からない時

プログラミング×稼げる副業スキルはテックキャンプ

プログラミングについて分からない時

【コエテコ様限定】※ご案内を受けた以外のメディアが使用しても成果は承認されません。
ミニマリスト_カミ

僕への個人でもメンターでも、スクールでもお好きな方を活用ください。

Laravel,PHPDTO,Laravel

Posted by kami