Skip to content

feat: MapとIMapFactoryにtryFromResultsメソッドを追加し、Resultからのインスタンス生成をサポート #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Collection/ArrayList.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
50 changes: 50 additions & 0 deletions src/Collection/Map.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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<TTryFromKey,IErrorValue>,Result<TTryFromValue,IErrorValue>>|Pair) ...$values
* @return Result<static<TTryFromKey,TTryFromValue>,ValueObjectError>
*/
/**
* @phpstan-ignore-next-line
*/
#[Override]
final public static function tryFromResults(Pair ...$values): Result
{
/**
* @var array<int,Result<TTryFromKey,IErrorValue>>
* @phpstan-ignore-next-line
* */
$keysResults = array_map(static fn ($pair) => $pair->key, $values);

/**
* @var array<int,Result<TTryFromValue,IErrorValue>>
* @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<int,Pair<TTryFromKey,TTryFromValue>> */
$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
{
Expand Down
12 changes: 12 additions & 0 deletions src/Collection/Map/IMapFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use WizDevelop\PhpMonad\Result;
use WizDevelop\PhpValueObject\Collection\Pair;
use WizDevelop\PhpValueObject\Error\IErrorValue;
use WizDevelop\PhpValueObject\Error\ValueObjectError;

/**
Expand Down Expand Up @@ -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<TTryFromKey,IErrorValue>,Result<TTryFromValue,IErrorValue>>|Pair) ...$values
* @return Result<static<TTryFromKey,TTryFromValue>,ValueObjectError>
*/
public static function tryFromResults(Pair ...$values): Result; /** @phpstan-ignore-line */

/**
* 空のコレクションを作成する
* @return static<TKey,TValue>
Expand Down
214 changes: 214 additions & 0 deletions tests/Unit/Collection/MapFromResultsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<?php

declare(strict_types=1);

namespace WizDevelop\PhpValueObject\Tests\Unit\Collection;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
use WizDevelop\PhpMonad\Result;
use WizDevelop\PhpValueObject\Collection\Map;
use WizDevelop\PhpValueObject\Collection\Pair;
use WizDevelop\PhpValueObject\Error\ValueObjectError;
use WizDevelop\PhpValueObject\Number\IntegerValue;
use WizDevelop\PhpValueObject\String\StringValue;

#[TestDox('Map::tryFromResults メソッドのテスト')]
#[CoversClass(Map::class)]
final class MapFromResultsTest extends TestCase
{
#[Test]
public function 成功したResultのPairから正常にMapが作成できる(): void
{
// 成功したResultのキーと値を持つPairを作成
$pairs = [
Pair::of(Result\ok('a'), Result\ok(1)),
Pair::of(Result\ok('b'), Result\ok(2)),
Pair::of(Result\ok('c'), Result\ok(3)),
];

// tryFromResults メソッドを呼び出す
$result = Map::tryFromResults(...$pairs);

// 結果が成功であることを確認
$this->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());
}
}
Loading