猫でもわかるWebプログラミングと副業

本業エンジニアリングマネージャー。副業Webエンジニア。Web開発のヒントや、副業、日常生活のことを書きます。

PHPでボトムアップDDD: 値オブジェクト / ValueObject

値オブジェクトを使わず、プリミティブな型を使うとどうなるか

ブログシステムを考えましょう。

  • ユーザーがいて、ユーザIDで区別する
  • 記事があって、記事IDで区別するとします

両方とも int だとします。

<?php

$userId = 1234;
$articleId = 5678;

ArticleRepository があり、ブログ記事を削除する場合、こんな感じになります。

<?php

class ArticleRepository 
{
    public function delete(int $articleId)
    {
        // 記事を削除する処理
    }
}

$userId = 1234;
$articleId = 5678;

/** @var ArticleRepository $articleRepository */
$articleRepository->delete($articleId);

このコードにはどんな問題点があるでしょうか。

各IDの型が分かりづらい

こういう事ができてしまいます。

<?php

$articleId = 'abs1234';

$articleId の型定義がどこにもないからです。

$articleRepository->delete($articleId); を呼び出して初めて int 型であることに気づくでしょう。

delete メソッドの引数の型チェックが弱い

こういうことが起こってしまいます。

$userId = 1234;
$articleId = 5678;

/** @var ArticleRepository $articleRepository */
$articleRepository->delete($userId);

間違えて、deleteメソッドに userId を入れてしまっています。しかし、これはエラーにはなりません。代わりに、記事ID 1234 の記事が消えてしまいます。

そこで値オブジェクト

値オブジェクト、英語では ValueObject と呼ばれるものを PHP で導入するとこうなります。

<?php
class UserId
{
    /** @var int */
    private $value;
    
    public function __construct(int $value)
    {
        $this->value = $value;
    }
    
    public function getValue(): int
    {
        return $this->value;
    }
}

class ArticleId
{
    /** @var int */
    private $value;

    public function __construct(int $value)
    {
        $this->value = $value;
    }

    public function getValue(): int
    {
        return $this->value;
    }
}

これを利用すると、先程までの実装はこうなります。

<?php

class ArticleRepository 
{
    // 型の制約が強くなる
    public function delete(ArticleId $articleId)
    {
        // 記事を削除する処理
    }
}

$userId = new UserId(1234);
$articleId = new ArticleId(5678);

/** @var ArticleRepository $articleRepository */
$articleRepository->delete($articleId);

これで、

  • userId は int であることが UserId クラスに定義されます
    • 他に制約がある場合(マイナスの値はNG等)、ここに追加できます。
  • userId と articleId を取り違えて articleRepository に渡すことはなくなります

さいごに

他にも値オブジェクトのありがたい点は色々あります。

以下の本が非常によくまとまっているので、読んでみてください。