diff --git a/composer.json b/composer.json index 75050db..72e637b 100644 --- a/composer.json +++ b/composer.json @@ -45,12 +45,12 @@ }, "require": { "php": "^8.3", - "tiny-blocks/mapper": "^1.1" + "tiny-blocks/mapper": "^1.2" }, "require-dev": { "phpmd/phpmd": "^2.15", - "phpunit/phpunit": "^11.5", - "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^12.1", + "phpstan/phpstan": "^2.1", "infection/infection": "^0.32", "squizlabs/php_codesniffer": "^3.13" }, diff --git a/src/Internal/Operations/Filter/Filter.php b/src/Internal/Operations/Filter/Filter.php index 560ad4a..6bae486 100644 --- a/src/Internal/Operations/Filter/Filter.php +++ b/src/Internal/Operations/Filter/Filter.php @@ -14,7 +14,7 @@ private function __construct(?Closure ...$predicates) { - $this->predicates = $predicates; + $this->predicates = array_filter($predicates); } public static function from(?Closure ...$predicates): Filter diff --git a/src/Internal/Operations/Retrieve/Get.php b/src/Internal/Operations/Retrieve/Get.php index 60c685b..49218f3 100644 --- a/src/Internal/Operations/Retrieve/Get.php +++ b/src/Internal/Operations/Retrieve/Get.php @@ -19,18 +19,10 @@ public static function from(iterable $elements): Get public function elementAtIndex(int $index, mixed $defaultValueIfNotFound): mixed { - if ($index < 0) { - return $defaultValueIfNotFound; - } - - $currentIndex = 0; - - foreach ($this->elements as $value) { + foreach ($this->elements as $currentIndex => $value) { if ($currentIndex === $index) { return $value; } - - $currentIndex++; } return $defaultValueIfNotFound; diff --git a/tests/Operations/Filter/CollectionFilterOperationTest.php b/tests/Operations/Filter/CollectionFilterOperationTest.php index 6863628..425f7aa 100644 --- a/tests/Operations/Filter/CollectionFilterOperationTest.php +++ b/tests/Operations/Filter/CollectionFilterOperationTest.php @@ -33,6 +33,42 @@ public function testFilterAppliesMultiplePredicates(): void self::assertSame([4], $actual->toArray(keyPreservation: KeyPreservation::DISCARD)); } + public function testFilterFailsIfPredicatesAreNotReindex(): void + { + /** @Given a collection with elements */ + $collection = Collection::createFrom(elements: [10, 20, 30]); + + /** @When filtering with a null predicate in the first position. */ + $actual = $collection->filter( + null, + static fn(int $value): bool => $value > 15 + ); + + /** @Then the filter should still work correctly. */ + $elements = iterator_to_array($actual); + + self::assertCount(2, $elements); + self::assertContains(20, $elements); + self::assertContains(30, $elements); + } + + public function testFilterIgnoresNullPredicatesAndReindexesWeights(): void + { + /** @Given a collection with elements */ + $collection = Collection::createFrom(elements: [1, 2, 3, 4, 5]); + + /** @When filtering using null values interspersed with valid predicates. */ + $actual = $collection->filter( + null, + static fn(int $value): bool => $value > 2, + null, + static fn(int $value): bool => $value < 5 + ); + + /** @Then it should only apply the valid predicates (result should be 3 and 4) */ + self::assertSame([3, 4], $actual->toArray(keyPreservation: KeyPreservation::DISCARD)); + } + #[DataProvider('elementsDataProvider')] public function testFilterAppliesDefaultArrayFilter(iterable $elements, iterable $expected): void { diff --git a/tests/Operations/Retrieve/CollectionGetOperationTest.php b/tests/Operations/Retrieve/CollectionGetOperationTest.php index 0d3b89e..2a92644 100644 --- a/tests/Operations/Retrieve/CollectionGetOperationTest.php +++ b/tests/Operations/Retrieve/CollectionGetOperationTest.php @@ -61,6 +61,25 @@ public function testGetByIndexReturnsNullForNegativeIndex(): void self::assertNull($actual); } + public function testGetByIndexReturnsDefaultValueWhenIndexIsNegative(): void + { + /** @Given a collection with elements */ + $collection = Collection::createFrom(elements: [ + new CryptoCurrency(name: 'Bitcoin', price: 60000.0, symbol: 'BTC'), + new CryptoCurrency(name: 'Ethereum', price: 40000.0, symbol: 'ETH'), + new CryptoCurrency(name: 'Binance Coin', price: 1500.0, symbol: 'BNB') + ]); + + /** @And a default value when the element is not found */ + $defaultValue = 'not-found'; + + /** @When attempting to get an element at a negative index */ + $actual = $collection->getBy(index: -1, defaultValueIfNotFound: $defaultValue); + + /** @Then the default value should be returned */ + self::assertSame($defaultValue, $actual); + } + public function testGetByIndexReturnsNullForEmptyCollection(): void { /** @Given an empty collection */