From 7155f7d617851f4a1965e121fe0dede8474aaec1 Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Mon, 12 May 2025 17:55:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Map=E3=81=A8IMapFactory=E3=81=ABtryFrom?= =?UTF-8?q?Results=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=97=E3=80=81Result=E3=81=8B=E3=82=89=E3=81=AE?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=B9=E3=82=BF=E3=83=B3=E3=82=B9=E7=94=9F?= =?UTF-8?q?=E6=88=90=E3=82=92=E3=82=B5=E3=83=9D=E3=83=BC=E3=83=88=20Fixes?= =?UTF-8?q?=20#27?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Collection/ArrayList.php | 1 + src/Collection/Map.php | 50 +++++ src/Collection/Map/IMapFactory.php | 12 ++ tests/Unit/Collection/MapFromResultsTest.php | 214 +++++++++++++++++++ 4 files changed, 277 insertions(+) create mode 100644 tests/Unit/Collection/MapFromResultsTest.php diff --git a/src/Collection/ArrayList.php b/src/Collection/ArrayList.php index e447378..add476e 100644 --- a/src/Collection/ArrayList.php +++ b/src/Collection/ArrayList.php @@ -18,6 +18,7 @@ use WizDevelop\PhpValueObject\Collection\Exception\MultipleCollectionsFoundException; use WizDevelop\PhpValueObject\Collection\List\IArrayList; use WizDevelop\PhpValueObject\Collection\List\IArrayListFactory; +use WizDevelop\PhpValueObject\Error\IErrorValue; use WizDevelop\PhpValueObject\Error\ValueObjectError; /** diff --git a/src/Collection/Map.php b/src/Collection/Map.php index 1b995b7..47ce378 100644 --- a/src/Collection/Map.php +++ b/src/Collection/Map.php @@ -20,6 +20,8 @@ use WizDevelop\PhpValueObject\Collection\Exception\MultipleCollectionsFoundException; use WizDevelop\PhpValueObject\Collection\Map\IMap; use WizDevelop\PhpValueObject\Collection\Map\IMapFactory; +use WizDevelop\PhpValueObject\Error\IErrorValue; +use WizDevelop\PhpValueObject\Error\ValueObjectError; use WizDevelop\PhpValueObject\IValueObject; /** @@ -152,6 +154,54 @@ final public static function tryFrom(Pair ...$values): Result ->andThen(static fn () => Result\ok(static::from(...$values))); } + /** + * 信頼できるプリミティブ値からインスタンスを生成する + * + * @template TTryFromKey of TKey + * @template TTryFromValue of TValue + * + * @param (Pair,Result>|Pair) ...$values + * @return Result,ValueObjectError> + */ + /** + * @phpstan-ignore-next-line + */ + #[Override] + final public static function tryFromResults(Pair ...$values): Result + { + /** + * @var array> + * @phpstan-ignore-next-line + * */ + $keysResults = array_map(static fn ($pair) => $pair->key, $values); + + /** + * @var array> + * @phpstan-ignore-next-line + */ + $valuesResults = array_map(static fn ($pair) => $pair->value, $values); + + $pairsResult = Result\combineWithErrorValue( + ...$keysResults, + ...$valuesResults + ); + + if ($pairsResult->isErr()) { + return Result\err(ValueObjectError::collection()->invalidElementValues( + static::class, + ...$pairsResult->unwrapErr() + )); + } + + /** @var array> */ + $values = array_map(static fn ($pair) => Pair::of($pair->key->unwrap(), $pair->value->unwrap()), $values); // @phpstan-ignore-line + + // @phpstan-ignore return.type + return static::isValid($values) // @phpstan-ignore argument.type + ->andThen(static fn () => static::isValidCount($values)) // @phpstan-ignore argument.type + ->andThen(static fn () => Result\ok(static::from(...$values))); // @phpstan-ignore-line + } + #[Override] final public static function empty(): static { diff --git a/src/Collection/Map/IMapFactory.php b/src/Collection/Map/IMapFactory.php index 36b34a4..8ff2ce0 100644 --- a/src/Collection/Map/IMapFactory.php +++ b/src/Collection/Map/IMapFactory.php @@ -6,6 +6,7 @@ use WizDevelop\PhpMonad\Result; use WizDevelop\PhpValueObject\Collection\Pair; +use WizDevelop\PhpValueObject\Error\IErrorValue; use WizDevelop\PhpValueObject\Error\ValueObjectError; /** @@ -39,6 +40,17 @@ public static function from(Pair ...$values): static; */ public static function tryFrom(Pair ...$values): Result; + /** + * 信頼できるプリミティブ値からインスタンスを生成する + * + * @template TTryFromKey of TKey + * @template TTryFromValue of TValue + * + * @param (Pair,Result>|Pair) ...$values + * @return Result,ValueObjectError> + */ + public static function tryFromResults(Pair ...$values): Result; /** @phpstan-ignore-line */ + /** * 空のコレクションを作成する * @return static diff --git a/tests/Unit/Collection/MapFromResultsTest.php b/tests/Unit/Collection/MapFromResultsTest.php new file mode 100644 index 0000000..7e34464 --- /dev/null +++ b/tests/Unit/Collection/MapFromResultsTest.php @@ -0,0 +1,214 @@ +assertTrue($result->isOk()); + + // 正しく要素が取り出せることを確認 + $map = $result->unwrap(); + $this->assertInstanceOf(Map::class, $map); + $this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3], $map->toArray()); + } + + #[Test] + public function 成功したバリューオブジェクトのResultのPairから正常にMapが作成できる(): void + { + // 成功したバリューオブジェクトのResultのPairを作成 + $pairs = [ + Pair::of(Result\ok(StringValue::from('key1')), Result\ok(IntegerValue::from(10))), + Pair::of(Result\ok(StringValue::from('key2')), Result\ok(IntegerValue::from(20))), + Pair::of(Result\ok(StringValue::from('key3')), Result\ok(IntegerValue::from(30))), + ]; + + // tryFromResults メソッドを呼び出す + $result = Map::tryFromResults(...$pairs); + + // 結果が成功であることを確認 + $this->assertTrue($result->isOk()); + + // 正しく要素が取り出せることを確認 + $map = $result->unwrap(); + $this->assertInstanceOf(Map::class, $map); + + // マップから各要素を取得して検証 + $this->assertCount(3, $map->toArray()); + + // キーと値を検証(特殊なキーの処理を考慮) + $this->assertTrue($map->has(StringValue::from('key1'))); + $this->assertTrue($map->has(StringValue::from('key2'))); + $this->assertTrue($map->has(StringValue::from('key3'))); + + // StringValueのキーで値を取得 + $value1 = $map->get(StringValue::from('key1'))->unwrap(); + $value2 = $map->get(StringValue::from('key2'))->unwrap(); + $value3 = $map->get(StringValue::from('key3'))->unwrap(); + + $this->assertInstanceOf(IntegerValue::class, $value1); + $this->assertInstanceOf(IntegerValue::class, $value2); + $this->assertInstanceOf(IntegerValue::class, $value3); + $this->assertEquals(10, $value1->value); + $this->assertEquals(20, $value2->value); + $this->assertEquals(30, $value3->value); + } + + #[Test] + public function 異なる型のバリューオブジェクトのResultのPairから正常にMapが作成できる(): void + { + // 異なる型のバリューオブジェクトのResultのPairを作成 + $pairs = [ + Pair::of(Result\ok(IntegerValue::from(1)), Result\ok(StringValue::from('value1'))), + Pair::of(Result\ok(StringValue::from('key2')), Result\ok(IntegerValue::from(2))), + Pair::of(Result\ok(IntegerValue::from(3)), Result\ok(StringValue::from('value3'))), + ]; + + // tryFromResults メソッドを呼び出す + $result = Map::tryFromResults(...$pairs); + + // 結果が成功であることを確認 + $this->assertTrue($result->isOk()); + + // 正しく要素が取り出せることを確認 + $map = $result->unwrap(); + $this->assertInstanceOf(Map::class, $map); + + // 各キーの存在を確認 + $this->assertTrue($map->has(IntegerValue::from(1))); + $this->assertTrue($map->has(StringValue::from('key2'))); + $this->assertTrue($map->has(IntegerValue::from(3))); + + // キーを使って値を取得して検証 + $value1 = $map->get(IntegerValue::from(1))->unwrap(); + $value2 = $map->get(StringValue::from('key2'))->unwrap(); + $value3 = $map->get(IntegerValue::from(3))->unwrap(); + + $this->assertInstanceOf(StringValue::class, $value1); + $this->assertInstanceOf(IntegerValue::class, $value2); + $this->assertInstanceOf(StringValue::class, $value3); + $this->assertEquals('value1', $value1->value); + $this->assertEquals(2, $value2->value); + $this->assertEquals('value3', $value3->value); + } + + #[Test] + public function キーに失敗したResultが含まれる場合はエラーが返される(): void + { + // キーに失敗したResultを含むPairの配列を作成 + $pairs = [ + Pair::of(Result\ok('a'), Result\ok(1)), + Pair::of(Result\err(ValueObjectError::general()->invalid('キーエラー')), Result\ok(2)), + Pair::of(Result\ok('c'), Result\ok(3)), + ]; + + // tryFromResults メソッドを呼び出す + $result = Map::tryFromResults(...$pairs); + + // 結果が失敗であることを確認 + $this->assertTrue($result->isErr()); + + // エラー情報を検証 + $error = $result->unwrapErr(); + $this->assertInstanceOf(ValueObjectError::class, $error); + $this->assertStringContainsString('無効な要素が含まれています', $error->getMessage()); + $this->assertStringContainsString('キーエラー', $error->getMessage()); + } + + #[Test] + public function 値に失敗したResultが含まれる場合はエラーが返される(): void + { + // 値に失敗したResultを含むPairの配列を作成 + $pairs = [ + Pair::of(Result\ok('a'), Result\ok(1)), + Pair::of(Result\ok('b'), Result\err(ValueObjectError::general()->invalid('値エラー'))), + Pair::of(Result\ok('c'), Result\ok(3)), + ]; + + // tryFromResults メソッドを呼び出す + $result = Map::tryFromResults(...$pairs); + + // 結果が失敗であることを確認 + $this->assertTrue($result->isErr()); + + // エラー情報を検証 + $error = $result->unwrapErr(); + $this->assertInstanceOf(ValueObjectError::class, $error); + $this->assertStringContainsString('無効な要素が含まれています', $error->getMessage()); + $this->assertStringContainsString('値エラー', $error->getMessage()); + } + + #[Test] + public function キーと値の両方に失敗したResultが含まれる場合は全てのエラーが集約される(): void + { + // キーと値の両方に失敗したResultを含むPairの配列を作成 + $pairs = [ + Pair::of(Result\ok('a'), Result\ok(1)), + Pair::of(Result\err(ValueObjectError::general()->invalid('キーエラー1')), Result\ok(2)), + Pair::of(Result\ok('c'), Result\err(ValueObjectError::general()->invalid('値エラー1'))), + Pair::of(Result\err(ValueObjectError::general()->invalid('キーエラー2')), Result\err(ValueObjectError::general()->invalid('値エラー2'))), + ]; + + // tryFromResults メソッドを呼び出す + $result = Map::tryFromResults(...$pairs); + + // 結果が失敗であることを確認 + $this->assertTrue($result->isErr()); + + // エラー情報を検証 + $error = $result->unwrapErr(); + $this->assertInstanceOf(ValueObjectError::class, $error); + $this->assertStringContainsString('無効な要素が含まれています', $error->getMessage()); + $this->assertStringContainsString('キーエラー1', $error->getMessage()); + $this->assertStringContainsString('値エラー1', $error->getMessage()); + $this->assertStringContainsString('キーエラー2', $error->getMessage()); + $this->assertStringContainsString('値エラー2', $error->getMessage()); + } + + #[Test] + public function 空のPair配列から空のMapが作成できる(): void + { + // 空の配列を作成 + $pairs = []; + + // tryFromResults メソッドを呼び出す + $result = Map::tryFromResults(...$pairs); + + // 結果が成功であることを確認 + $this->assertTrue($result->isOk()); + + // 空のマップであることを確認 + $map = $result->unwrap(); + $this->assertInstanceOf(Map::class, $map); + $this->assertEmpty($map->toArray()); + $this->assertEquals(0, $map->count()); + } +}