【Laravel】DTO(Data Transfer Object)クラスを使った例の紹介
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
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
ユーザーに関するユースケースを定義するインターフェースです。
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)クラスです。
リポジトリから取得したデータを元に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アプリ開発で分からない時
プログラミング×稼げる副業スキルはテックキャンププログラミングについて分からない時
【コエテコ様限定】※ご案内を受けた以外のメディアが使用しても成果は承認されません。僕への個人でもメンターでも、スクールでもお好きな方を活用ください。