From 4cded0dadda2cfdeea4628dc17055c8853f219b0 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 04:43:36 +0000 Subject: [PATCH 1/5] feat: Add filterStrict() and flatten() methods to ArrayList - Add filterStrict() method that returns static (preserves exact type) - Modify filter() method to return self (ArrayList) instead of static - Add flatten() method to convert 2D arrays to 1D - Update IArrayList interface with new method signatures - Add comprehensive tests for all new methods Fixes #40 Co-authored-by: kakiuchi-shigenao --- src/Collection/ArrayList.php | 25 ++++++++++- src/Collection/List/IArrayList.php | 16 ++++++- tests/Unit/Collection/ArrayListTest.php | 56 +++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/Collection/ArrayList.php b/src/Collection/ArrayList.php index 499b163..5408f9f 100644 --- a/src/Collection/ArrayList.php +++ b/src/Collection/ArrayList.php @@ -338,9 +338,14 @@ final public function mapStrict(Closure $closure): static } #[Override] - final public function filter(Closure $closure): static + final public function filter(Closure $closure): self { + return new self(array_filter($this->elements, $closure, ARRAY_FILTER_USE_BOTH)); + } + #[Override] + final public function filterStrict(Closure $closure): static + { return new static(array_filter($this->elements, $closure, ARRAY_FILTER_USE_BOTH)); } @@ -436,4 +441,22 @@ final public function sort(?Closure $closure = null): static return new static($elements); } + + #[Override] + final public function flatten(): self + { + $result = []; + + foreach ($this->elements as $item) { + if (is_array($item)) { + foreach ($item as $subItem) { + $result[] = $subItem; + } + } else { + $result[] = $item; + } + } + + return new self($result); + } } diff --git a/src/Collection/List/IArrayList.php b/src/Collection/List/IArrayList.php index 6ed538a..91d8778 100644 --- a/src/Collection/List/IArrayList.php +++ b/src/Collection/List/IArrayList.php @@ -134,9 +134,17 @@ public function mapStrict(Closure $closure): static; /** * 与えられた真理判定に合格するすべての要素のコレクションを作成する。 * @param Closure(TValue,int): bool $closure + * @return self + */ + public function filter(Closure $closure): self; + + /** + * 与えられた真理判定に合格するすべての要素のコレクションを作成する。 + * (strict version - 正確な型を保持) + * @param Closure(TValue,int): bool $closure * @return static */ - public function filter(Closure $closure): static; + public function filterStrict(Closure $closure): static; /** * 与えられた真理判定に合格しないすべての要素のコレクションを作成する。 @@ -186,4 +194,10 @@ public function add($element): static; * @return static */ public function sort(?Closure $closure = null): static; + + /** + * 2次元配列を1次元に変換する + * @return self + */ + public function flatten(): self; } diff --git a/tests/Unit/Collection/ArrayListTest.php b/tests/Unit/Collection/ArrayListTest.php index 7cb2301..6a0238e 100644 --- a/tests/Unit/Collection/ArrayListTest.php +++ b/tests/Unit/Collection/ArrayListTest.php @@ -467,4 +467,60 @@ public function sort関数で要素をソートしたコレクションが取得 // 元のコレクションは変更されない(イミュータブル) $this->assertEquals([3, 1, 4, 2, 5], $collection->toArray()); } + + #[Test] + public function filter関数でselfを返すことができる(): void + { + $collection = ArrayList::from([1, 2, 3, 4, 5]); + + $filtered = $collection->filter(static fn ($value) => $value % 2 === 0); + + // 戻り値がArrayListインスタンス(self)であることを確認 + $this->assertInstanceOf(ArrayList::class, $filtered); + $this->assertEquals([1 => 2, 3 => 4], $filtered->toArray()); + + // 元のコレクションは変更されない(イミュータブル) + $this->assertEquals([1, 2, 3, 4, 5], $collection->toArray()); + } + + #[Test] + public function filterStrict関数でstaticを返すことができる(): void + { + $collection = ArrayList::from([1, 2, 3, 4, 5]); + + $filtered = $collection->filterStrict(static fn ($value) => $value % 2 === 0); + + // 戻り値が正確な型(static)であることを確認 + $this->assertInstanceOf(ArrayList::class, $filtered); + $this->assertEquals([1 => 2, 3 => 4], $filtered->toArray()); + + // 元のコレクションは変更されない(イミュータブル) + $this->assertEquals([1, 2, 3, 4, 5], $collection->toArray()); + } + + #[Test] + public function flatten関数で2次元配列を1次元に変換できる(): void + { + // 基本的な2次元配列 + $collection = ArrayList::from([[1, 2], [3, 4], [5, 6]]); + $flattened = $collection->flatten(); + + $this->assertInstanceOf(ArrayList::class, $flattened); + $this->assertEquals([1, 2, 3, 4, 5, 6], $flattened->toArray()); + + // 空の配列を含む場合 + $collectionWithEmpty = ArrayList::from([[1, 2], [], [3, 4]]); + $flattenedWithEmpty = $collectionWithEmpty->flatten(); + + $this->assertEquals([1, 2, 3, 4], $flattenedWithEmpty->toArray()); + + // 混合型(配列と非配列要素) + $mixedCollection = ArrayList::from([[1, 2], 3, [4, 5], 6]); + $flattenedMixed = $mixedCollection->flatten(); + + $this->assertEquals([1, 2, 3, 4, 5, 6], $flattenedMixed->toArray()); + + // 元のコレクションは変更されない(イミュータブル) + $this->assertEquals([[1, 2], [3, 4], [5, 6]], $collection->toArray()); + } } From 7174bb5b7c5b3d692b0fd6d8d675544edf783b53 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 04:55:34 +0000 Subject: [PATCH 2/5] fix: Resolve type inconsistencies in filter methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix sole() method to use filterStrict() for static return type consistency - Fix reject() method to use filterStrict() since it returns static - Fix ValueObjectList remove() and diff() methods to use filterStrict() - Ensures type safety between filter() (returns self) and filterStrict() (returns static) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: kakiuchi-shigenao --- src/Collection/ArrayList.php | 4 ++-- src/ValueObjectList.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Collection/ArrayList.php b/src/Collection/ArrayList.php index 5408f9f..eb02558 100644 --- a/src/Collection/ArrayList.php +++ b/src/Collection/ArrayList.php @@ -253,7 +253,7 @@ final public function firstOrFail(?Closure $closure = null) #[Override] final public function sole(?Closure $closure = null) { - $items = $closure === null ? new static($this->elements) : $this->filter($closure); + $items = $closure === null ? new static($this->elements) : $this->filterStrict($closure); $count = $items->count(); if ($count === 0) { @@ -352,7 +352,7 @@ final public function filterStrict(Closure $closure): static #[Override] final public function reject(Closure $closure): static { - return $this->filter(static fn ($value, $key) => !$closure($value, $key)); + return $this->filterStrict(static fn ($value, $key) => !$closure($value, $key)); } #[Override] diff --git a/src/ValueObjectList.php b/src/ValueObjectList.php index 1014f90..c40f67f 100644 --- a/src/ValueObjectList.php +++ b/src/ValueObjectList.php @@ -27,7 +27,7 @@ public function has(IValueObject $element): bool */ public function remove(IValueObject $element): static { - return $this->filter(static fn (IValueObject $e) => !$e->equals($element)); + return $this->filterStrict(static fn (IValueObject $e) => !$e->equals($element)); } /** @@ -45,6 +45,6 @@ public function put(IValueObject $element): static */ public function diff(self $other): static { - return $this->filter(static fn (IValueObject $e) => !$other->has($e)); + return $this->filterStrict(static fn (IValueObject $e) => !$other->has($e)); } } From c2f016738c57d0b9258f7c6ab8d31ff634b17b70 Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Wed, 16 Jul 2025 15:27:37 +0900 Subject: [PATCH 3/5] feat: Implement flatMap method in ArrayList and update IArrayList interface --- phpunit.xml.dist | 7 +++ src/Collection/ArrayList.php | 43 +++++++++------- src/Collection/List/IArrayList.php | 14 ++--- tests/Unit/Collection/ArrayListTest.php | 68 +++++++++++++++---------- 4 files changed, 82 insertions(+), 50 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 28dab84..e8404c2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,6 +12,13 @@ timeoutForMediumTests="10" timeoutForLargeTests="60" > + + + + + + + tests/Unit diff --git a/src/Collection/ArrayList.php b/src/Collection/ArrayList.php index eb02558..8ef59dd 100644 --- a/src/Collection/ArrayList.php +++ b/src/Collection/ArrayList.php @@ -337,6 +337,31 @@ final public function mapStrict(Closure $closure): static return new static(array_map($closure, $this->elements, $keys)); } + /** + * @template TFlatMapValue + * @param Closure(TValue,int): iterable $closure + * @return self + */ + #[Override] + final public function flatMap(Closure $closure): self + { + $result = []; + + foreach ($this->elements as $index => $item) { + $mapped = $closure($item, $index); + + foreach ($mapped as $subItem) { + $result[] = $subItem; + } + } + + return new self($result); + } + + /** + * @param Closure(TValue,int): bool $closure + * @return self + */ #[Override] final public function filter(Closure $closure): self { @@ -441,22 +466,4 @@ final public function sort(?Closure $closure = null): static return new static($elements); } - - #[Override] - final public function flatten(): self - { - $result = []; - - foreach ($this->elements as $item) { - if (is_array($item)) { - foreach ($item as $subItem) { - $result[] = $subItem; - } - } else { - $result[] = $item; - } - } - - return new self($result); - } } diff --git a/src/Collection/List/IArrayList.php b/src/Collection/List/IArrayList.php index 91d8778..28d8fea 100644 --- a/src/Collection/List/IArrayList.php +++ b/src/Collection/List/IArrayList.php @@ -125,6 +125,14 @@ public function merge(self $other): self; */ public function map(Closure $closure): self; + /** + * 各要素に関数を適用し、結果を平坦化したコレクションを返す + * @template TFlatMapValue + * @param Closure(TValue,int): iterable $closure + * @return self + */ + public function flatMap(Closure $closure): self; + /** * @param Closure(TValue,int): TValue $closure * @return static @@ -194,10 +202,4 @@ public function add($element): static; * @return static */ public function sort(?Closure $closure = null): static; - - /** - * 2次元配列を1次元に変換する - * @return self - */ - public function flatten(): self; } diff --git a/tests/Unit/Collection/ArrayListTest.php b/tests/Unit/Collection/ArrayListTest.php index 6a0238e..8da63fc 100644 --- a/tests/Unit/Collection/ArrayListTest.php +++ b/tests/Unit/Collection/ArrayListTest.php @@ -472,13 +472,13 @@ public function sort関数で要素をソートしたコレクションが取得 public function filter関数でselfを返すことができる(): void { $collection = ArrayList::from([1, 2, 3, 4, 5]); - + $filtered = $collection->filter(static fn ($value) => $value % 2 === 0); - + // 戻り値がArrayListインスタンス(self)であることを確認 $this->assertInstanceOf(ArrayList::class, $filtered); $this->assertEquals([1 => 2, 3 => 4], $filtered->toArray()); - + // 元のコレクションは変更されない(イミュータブル) $this->assertEquals([1, 2, 3, 4, 5], $collection->toArray()); } @@ -487,40 +487,56 @@ public function filter関数でselfを返すことができる(): void public function filterStrict関数でstaticを返すことができる(): void { $collection = ArrayList::from([1, 2, 3, 4, 5]); - + $filtered = $collection->filterStrict(static fn ($value) => $value % 2 === 0); - + // 戻り値が正確な型(static)であることを確認 $this->assertInstanceOf(ArrayList::class, $filtered); $this->assertEquals([1 => 2, 3 => 4], $filtered->toArray()); - + // 元のコレクションは変更されない(イミュータブル) $this->assertEquals([1, 2, 3, 4, 5], $collection->toArray()); } #[Test] - public function flatten関数で2次元配列を1次元に変換できる(): void + public function flatMap関数で各要素を変換して平坦化できる(): void { - // 基本的な2次元配列 - $collection = ArrayList::from([[1, 2], [3, 4], [5, 6]]); - $flattened = $collection->flatten(); - - $this->assertInstanceOf(ArrayList::class, $flattened); + // 基本的な変換(各数値を2倍にして配列に包む) + $collection = ArrayList::from([1, 2, 3]); + $mapped = $collection->flatMap(static fn ($value) => [$value * 2]); + + $this->assertInstanceOf(ArrayList::class, $mapped); + $this->assertEquals([2, 4, 6], $mapped->toArray()); + + // 各要素を複数の要素に展開 + $collection2 = ArrayList::from([1, 2, 3]); + $expanded = $collection2->flatMap(static fn ($value) => [$value, $value * 10]); + + $this->assertEquals([1, 10, 2, 20, 3, 30], $expanded->toArray()); + + // 空の配列を返す場合 + $collection3 = ArrayList::from([1, 2, 3]); + $filtered = $collection3->flatMap(static fn ($value) => $value % 2 === 0 ? [$value] : []); + + $this->assertEquals([2], $filtered->toArray()); + + // 2次元配列の平坦化(従来のflattenと同等の動作) + $collection4 = ArrayList::from([[1, 2], [3, 4], [5, 6]]); + $flattened = $collection4->flatMap(static fn ($array) => $array); + $this->assertEquals([1, 2, 3, 4, 5, 6], $flattened->toArray()); - - // 空の配列を含む場合 - $collectionWithEmpty = ArrayList::from([[1, 2], [], [3, 4]]); - $flattenedWithEmpty = $collectionWithEmpty->flatten(); - - $this->assertEquals([1, 2, 3, 4], $flattenedWithEmpty->toArray()); - - // 混合型(配列と非配列要素) - $mixedCollection = ArrayList::from([[1, 2], 3, [4, 5], 6]); - $flattenedMixed = $mixedCollection->flatten(); - - $this->assertEquals([1, 2, 3, 4, 5, 6], $flattenedMixed->toArray()); - // 元のコレクションは変更されない(イミュータブル) - $this->assertEquals([[1, 2], [3, 4], [5, 6]], $collection->toArray()); + $this->assertEquals([1, 2, 3], $collection->toArray()); + + // objectの2次元配列の平坦化(従来のflattenと同等の動作) + $collection5 = ArrayList::from([ + ArrayList::from([1, 2]), + ArrayList::from([3, 4]), + ArrayList::from([5, 6]), + ]); + $flattenedObjects = $collection5->flatMap(static fn ($array) => $array); + $this->assertEquals([1, 2, 3, 4, 5, 6], $flattenedObjects->toArray()); + // 元のコレクションは変更されない(イミュータブル) + $this->assertEquals([ArrayList::from([1, 2]), ArrayList::from([3, 4]), ArrayList::from([5, 6])], $collection5->toArray()); } } From 3248fa489c56361cd9b83a72b283e872a039d370 Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Wed, 16 Jul 2025 15:51:01 +0900 Subject: [PATCH 4/5] fix: Enable assertions in PHPUnit configuration for CI environment --- .github/workflows/phpstan.yml | 2 +- phpunit.xml.dist | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index f17e42e..38dadbc 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -68,4 +68,4 @@ jobs: key: "phpstan-result-cache-${{ github.run_id }}" - name: Run PHPUnit tests - run: vendor/bin/phpunit --configuration=phpunit.xml.dist --testdox --colors=always + run: php -d zend.assertions=1 vendor/bin/phpunit --configuration=phpunit.xml.dist --testdox --colors=always diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e8404c2..28dab84 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,13 +12,6 @@ timeoutForMediumTests="10" timeoutForLargeTests="60" > - - - - - - - tests/Unit From 8e1da559004a6ca01b73dac3e6daa42c2aae4300 Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Wed, 16 Jul 2025 15:51:55 +0900 Subject: [PATCH 5/5] chore: Remove Claude Code Review workflow configuration --- .github/workflows/claude-code-review.yml | 78 ------------------------ 1 file changed, 78 deletions(-) delete mode 100644 .github/workflows/claude-code-review.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index 5bf8ce5..0000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Claude Code Review - -on: - pull_request: - types: [opened, synchronize] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" - -jobs: - claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@beta - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) - # model: "claude-opus-4-20250514" - - # Direct prompt for automated review (no @claude mention needed) - direct_prompt: | - Please review this pull request and provide feedback on: - - Code quality and best practices - - Potential bugs or issues - - Performance considerations - - Security concerns - - Test coverage - - Be constructive and helpful in your feedback. - - # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR - # use_sticky_comment: true - - # Optional: Customize review based on file types - # direct_prompt: | - # Review this PR focusing on: - # - For TypeScript files: Type safety and proper interface usage - # - For API endpoints: Security, input validation, and error handling - # - For React components: Performance, accessibility, and best practices - # - For tests: Coverage, edge cases, and test quality - - # Optional: Different prompts for different authors - # direct_prompt: | - # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' && - # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' || - # 'Please provide a thorough code review focusing on our coding standards and best practices.' }} - - # Optional: Add specific tools for running tests or linting - # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)" - - # Optional: Skip review for certain conditions - # if: | - # !contains(github.event.pull_request.title, '[skip-review]') && - # !contains(github.event.pull_request.title, '[WIP]') -