diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index f1abe3c..1b888e8 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -43,7 +43,7 @@ jobs: run: composer run-script ci-test - name: Upload coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage-report path: coverage.xml diff --git a/.gitignore b/.gitignore index 783d717..847bd3e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ vendor/ .idea/ coverage.xml package.xml +clover.xml coverage-html/ .phpunit/ /.php-cs-fixer.cache diff --git a/README.md b/README.md index 917badc..8bc6dbe 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ final readonly class UserData extends Data public function __construct( public string $firstName, #[Aliases('familyName')] - public stirng $lastName + public string $lastName ) { $this->fullName = "$this->firstName $this->lastName"; } @@ -67,10 +67,6 @@ This package was inspired from the [spatie/data-transfer-object](https://github. The main thing that I tried to focus on when creating this package is to make it outside of Laravel ecosystem, meaning: no dependency on [illuminate/support](https://github.com/illuminate/support). -**In no way** I am trying to compare this package with the original one, -Clearly, the original package is more advanced and has more features than this one, -and if you are using Laravel, I highly recommend using the original package instead of this one. - ### Requirements - PHP 8.4 or higher diff --git a/clover.xml b/clover.xml deleted file mode 100644 index 26f192e..0000000 --- a/clover.xml +++ /dev/null @@ -1,1664 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/composer.json b/composer.json index a09f12f..0a5b607 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ } }, "scripts": { - "ci-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=ci --configuration phpunit.xml", + "ci-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --testsuite=ci --configuration phpunit.xml", + "unit-test": "XDEBUG_MODE=coverage vendor/bin/phpunit --testsuite=unit --configuration phpunit.xml", "phpstan": "vendor/bin/phpstan analyse --configuration phpstan.neon --memory-limit=256M" } } diff --git a/docs/DataConfiguration.md b/docs/DataConfiguration.md index 2e73108..498756d 100644 --- a/docs/DataConfiguration.md +++ b/docs/DataConfiguration.md @@ -38,6 +38,6 @@ DataConfiguration::getInstance([ BackedEnumSerializer::class, ScalarTypeSerializer::class, ] - ] + ], ]) ``` diff --git a/src/Concerns/BaseData.php b/src/Concerns/BaseData.php index 2e9b0d1..7d42eeb 100644 --- a/src/Concerns/BaseData.php +++ b/src/Concerns/BaseData.php @@ -9,7 +9,6 @@ use Nuxtifyts\PhpDto\Normalizers\Concerns\HasNormalizers; use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipeline; use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipelinePassable; -use ReflectionClass; use Throwable; trait BaseData diff --git a/src/Configuration/DataConfiguration.php b/src/Configuration/DataConfiguration.php index cfc2046..3fd2221 100644 --- a/src/Configuration/DataConfiguration.php +++ b/src/Configuration/DataConfiguration.php @@ -36,7 +36,7 @@ public static function getInstance( serializers: SerializersConfiguration::getInstance( Arr::getArray($config ?? [], 'serializers'), $forceCreate - ) + ), ); } } diff --git a/src/Support/Arr.php b/src/Support/Arr.php index 0a83032..9bada21 100644 --- a/src/Support/Arr.php +++ b/src/Support/Arr.php @@ -2,11 +2,13 @@ namespace Nuxtifyts\PhpDto\Support; +use BackedEnum; +use InvalidArgumentException; + final readonly class Arr { /** * @param array $array - * @param string $key * @param array $default * * @return array @@ -30,4 +32,165 @@ public static function isArrayOfClassStrings(array $array, string $classString): && is_subclass_of($value, $classString) ); } + + /** + * @param array $array + */ + public static function getStringOrNull(array $array, string $key): ?string + { + $value = $array[$key] ?? null; + + return is_string($value) ? $value : null; + } + + /** + * @param array $array + */ + public static function getString(array $array, string $key, string $default = ''): string + { + return self::getStringOrNull($array, $key) ?? $default; + } + + /** + * @param array $array + */ + public static function getIntegerOrNull(array $array, string $key): ?int + { + $value = $array[$key] ?? null; + + return is_int($value) ? $value : null; + } + + /** + * @param array $array + */ + public static function getInteger(array $array, string $key, int $default = 0): int + { + return self::getIntegerOrNull($array, $key) ?? $default; + } + + /** + * @param array $array + */ + public static function getFloatOrNull(array $array, string $key): ?float + { + $value = $array[$key] ?? null; + + return is_float($value) ? $value : null; + } + + /** + * @param array $array + */ + public static function getFloat(array $array, string $key, float $default = 0.0): float + { + return self::getFloatOrNull($array, $key) ?? $default; + } + + /** + * @param array $array + */ + public static function getBooleanOrNull(array $array, string $key): ?bool + { + $value = $array[$key] ?? null; + + return is_bool($value) ? $value : null; + } + + /** + * @param array $array + */ + public static function getBoolean(array $array, string $key, bool $default = false): bool + { + return self::getBooleanOrNull($array, $key) ?? $default; + } + + /** + * @template T of BackedEnum + * + * @param array $array + * @param class-string $enumClass + * @param ?T $default + * + * @return ?T + */ + public static function getBackedEnumOrNull( + array $array, + string $key, + string $enumClass, + ?BackedEnum $default = null + ): ?BackedEnum { + $value = $array[$key] ?? null; + + if ($value instanceof $enumClass) { + return $value; + } else if ( + (is_string($value) || is_integer($value)) + && $resolvedValue = $enumClass::tryFrom($value) + ) { + return $resolvedValue; + } + + return is_null($default) + ? null + : ($default instanceof $enumClass + ? $default + : throw new InvalidArgumentException('Default value must be an instance of ' . $enumClass) + ); + } + + /** + * @template T of BackedEnum + * + * @param array $array + * @param class-string $enumClass + * @param T $default + * + * @return T + */ + public static function getBackedEnum( + array $array, + string $key, + string $enumClass, + BackedEnum $default + ): BackedEnum { + return self::getBackedEnumOrNull($array, $key, $enumClass, $default) ?? $default; + } + + + /** + * @param array $array + * + * @return ($preserveKeys is true ? array : list) + */ + public static function flatten(array $array, float $depth = INF, bool $preserveKeys = true): array + { + $result = []; + + foreach ($array as $key => $item) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (! is_array($item)) { + if ($preserveKeys) { + $result[$key] = $item; + } else { + $result[] = $item; + } + } else { + $values = $depth === 1.0 + ? $item + : self::flatten($item, $depth - 1, $preserveKeys); + + foreach ($values as $subKey => $value) { + if ($preserveKeys) { + $result[$subKey] = $value; + } else { + $result[] = $value; + } + } + } + } + + return $result; + } } diff --git a/src/Support/Collection.php b/src/Support/Collection.php new file mode 100644 index 0000000..47369c1 --- /dev/null +++ b/src/Support/Collection.php @@ -0,0 +1,117 @@ + */ + protected array $items = []; + + /** + * @param array $items + */ + public function __construct(array $items = []) + { + $this->items = $items; + } + + /** + * @param TValue $item + * + * @return self + */ + public function push(mixed $item): self + { + $this->items[] = $item; + return $this; + } + + /** + * @param TKey $key + * @param TValue $value + * + * @return self + */ + public function put(mixed $key, mixed $value): self + { + $this->items[$key] = $value; + return $this; + } + + /** + * @param ?callable(TValue $item): bool $callable + * + * @return ?TValue + */ + public function first(?callable $callable = null): mixed + { + return is_null($callable) + ? reset($this->items) ?: null + : array_find($this->items, $callable); + } + + /** + * @template TNewValue of mixed + * @param callable(TValue $item): TNewValue $callable + * + * @return self + */ + public function map(callable $callable): self + { + return new self(array_map($callable, $this->items)); + } + + /** + * @return ($preserveKeys is true ? Collection : Collection) + */ + public function collapse(bool $preserveKeys = false): self + { + return $this->flatten(1, $preserveKeys); + } + + /** + * @return ($preserveKeys is true ? Collection : Collection) + */ + public function flatten(float $depth = INF, bool $preserveKeys = true): self + { + return new self(Arr::flatten($this->items, $depth, $preserveKeys)); + } + + public function isNotEmpty(): bool + { + return !empty($this->items); + } + + public function isEmpty(): bool + { + return !$this->isNotEmpty(); + } + + /** + * @param callable(TValue $item): bool $callable + */ + public function every(callable $callable): bool + { + return array_all($this->items, $callable); + } + + /** + * @param callable(TValue $item): bool $callable + */ + public function some(callable $callable): bool + { + return array_any($this->items, $callable); + } + + /** + * @return array + */ + public function all(): array + { + return $this->items; + } +} diff --git a/tests/Unit/Support/ArrTest.php b/tests/Unit/Support/ArrTest.php index 633b923..59f77d6 100644 --- a/tests/Unit/Support/ArrTest.php +++ b/tests/Unit/Support/ArrTest.php @@ -2,16 +2,22 @@ namespace Nuxtifyts\PhpDto\Tests\Unit\Support; -use Nuxtifyts\PhpDto\Serializers\BackedEnumSerializer; -use Nuxtifyts\PhpDto\Serializers\ScalarTypeSerializer; -use Nuxtifyts\PhpDto\Serializers\Serializer; +use InvalidArgumentException; use Nuxtifyts\PhpDto\Support\Arr; +use PHPUnit\Framework\Attributes\Test; use Nuxtifyts\PhpDto\Tests\Unit\UnitCase; +use PHPUnit\Framework\Attributes\UsesClass; +use Nuxtifyts\PhpDto\Serializers\Serializer; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\Test; +use Nuxtifyts\PhpDto\Serializers\BackedEnumSerializer; +use Nuxtifyts\PhpDto\Serializers\ScalarTypeSerializer; +use Nuxtifyts\PhpDto\Tests\Dummies\Enums\YesNoBackedEnum; +use Nuxtifyts\PhpDto\Tests\Dummies\Enums\ColorsBackedEnum; #[CoversClass(Arr::class)] +#[UsesClass(YesNoBackedEnum::class)] +#[UsesClass(ColorsBackedEnum::class)] final class ArrTest extends UnitCase { /** @@ -20,6 +26,7 @@ final class ArrTest extends UnitCase #[Test] #[DataProvider('get_arr_provider')] #[DataProvider('is_array_of_class_strings_provider')] + #[DataProvider('flatten_provider')] public function arr_helper_functions( string $functionName, array $parameters, @@ -62,9 +69,281 @@ public static function get_arr_provider(): array ], [], ], + 'get string existing key, invalid value' => [ + 'getString', + [ + 'array' => ['key' => 1], + 'key' => 'key', + ], + '', + ], + 'get string existing key, valid value' => [ + 'getString', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + 'value', + ], + 'get string non-existing key' => [ + 'getString', + [ + 'array' => ['key' => 'value'], + 'key' => 'nonExistingKey', + ], + '', + ], + 'get string or null existing key, invalid value' => [ + 'getStringOrNull', + [ + 'array' => ['key' => 1], + 'key' => 'key', + ], + null, + ], + 'get string or null existing key, valid value' => [ + 'getStringOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + 'value', + ], + 'get string or null non-existing key' => [ + 'getStringOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'nonExistingKey', + ], + null, + ], + 'get integer existing key, invalid value' => [ + 'getInteger', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + 0, + ], + 'get integer existing key, valid value' => [ + 'getInteger', + [ + 'array' => ['key' => 1], + 'key' => 'key', + ], + 1, + ], + 'get integer non-existing key' => [ + 'getInteger', + [ + 'array' => ['key' => 1], + 'key' => 'nonExistingKey', + ], + 0, + ], + 'get integer or null existing key, invalid value' => [ + 'getIntegerOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + null, + ], + 'get integer or null existing key, valid value' => [ + 'getIntegerOrNull', + [ + 'array' => ['key' => 1], + 'key' => 'key', + ], + 1, + ], + 'get integer or null non-existing key' => [ + 'getIntegerOrNull', + [ + 'array' => ['key' => 1], + 'key' => 'nonExistingKey', + ], + null, + ], + 'get float existing key, invalid value' => [ + 'getFloat', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + 0.0, + ], + 'get float existing key, valid value' => [ + 'getFloat', + [ + 'array' => ['key' => 1.1], + 'key' => 'key', + ], + 1.1, + ], + 'get float non-existing key' => [ + 'getFloat', + [ + 'array' => ['key' => 1.1], + 'key' => 'nonExistingKey', + ], + 0.0, + ], + 'get float or null existing key, invalid value' => [ + 'getFloatOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + null, + ], + 'get float or null existing key, valid value' => [ + 'getFloatOrNull', + [ + 'array' => ['key' => 1.1], + 'key' => 'key', + ], + 1.1, + ], + 'get float or null non-existing key' => [ + 'getFloatOrNull', + [ + 'array' => ['key' => 1.1], + 'key' => 'nonExistingKey', + ], + null, + ], + 'get boolean existing key, invalid value' => [ + 'getBoolean', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + false, + ], + 'get boolean existing key, valid value' => [ + 'getBoolean', + [ + 'array' => ['key' => true], + 'key' => 'key', + ], + true, + ], + 'get boolean non-existing key' => [ + 'getBoolean', + [ + 'array' => ['key' => true], + 'key' => 'nonExistingKey', + ], + false, + ], + 'get boolean or null existing key, invalid value' => [ + 'getBooleanOrNull', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + null, + ], + 'get boolean or null existing key, valid value' => [ + 'getBooleanOrNull', + [ + 'array' => ['key' => true], + 'key' => 'key', + ], + true, + ], + 'get boolean or null non-existing key' => [ + 'getBooleanOrNull', + [ + 'array' => ['key' => true], + 'key' => 'nonExistingKey', + ], + null, + ], + 'get backed enum or null, invalid value' => [ + 'getBackedEnumOrNull', + [ + 'array' => ['key' => 'invalid'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + ], + null + ], + 'get backed enum or null, invalid value default provided' => [ + 'getBackedEnumOrNull', + [ + 'array' => ['key' => 'invalid'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + 'default' => YesNoBackedEnum::NO, + ], + YesNoBackedEnum::NO + ], + 'get backed enum or null, valid backed enum value' => [ + 'getBackedEnumOrNull', + [ + 'array' => ['key' => YesNoBackedEnum::YES], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + ], + YesNoBackedEnum::YES + ], + 'get backed enum or null, valid string value' => [ + 'getBackedEnumOrNull', + [ + 'array' => ['key' => 'yes'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + ], + YesNoBackedEnum::YES + ], + 'get backed enum, invalid value' => [ + 'getBackedEnum', + [ + 'array' => ['key' => 'invalid'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + 'default' => YesNoBackedEnum::NO, + ], + YesNoBackedEnum::NO + ], + 'get backed enum, valid backed enum value' => [ + 'getBackedEnum', + [ + 'array' => ['key' => YesNoBackedEnum::YES], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + 'default' => YesNoBackedEnum::NO, + ], + YesNoBackedEnum::YES + ], + 'get backed enum, valid string value' => [ + 'getBackedEnum', + [ + 'array' => ['key' => 'yes'], + 'key' => 'key', + 'enumClass' => YesNoBackedEnum::class, + 'default' => YesNoBackedEnum::NO, + ], + YesNoBackedEnum::YES + ], ]; } + #[Test] + public function get_backed_enum_or_null_will_throw_an_exception_if_default_value_is_invalid(): void + { + self::expectException(InvalidArgumentException::class); + + Arr::getBackedEnumOrNull( + ['key' => 'invalid'], + 'key', + YesNoBackedEnum::class, + ColorsBackedEnum::RED + ); + } + /** * @return array */ @@ -96,4 +375,124 @@ public static function is_array_of_class_strings_provider(): array ], ]; } + + /** + * @return array + */ + public static function flatten_provider(): array + { + return [ + 'flatten, empty array' => [ + 'flatten', + [ + 'array' => [], + ], + [] + ], + 'flatten array, one depth' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => 1.1, + 'a2' => 1.2 + ], + 'b' => 2, + ], + ], + [ + 'a1' => 1.1, + 'a2' => 1.2, + 'b' => 2 + ] + ], + 'flatten array, multiple depths' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => [ + 'a1.1' => 1.1, + 'a1.2' => 1.2 + ], + 'a2' => 2 + ], + 'b' => 3, + ], + ], + [ + 'a1.1' => 1.1, + 'a1.2' => 1.2, + 'a2' => 2, + 'b' => 3 + ] + ], + 'flatten array, and resets array keys' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => 1.1, + 'a2' => 1.2 + ], + 'b' => 2, + ], + 'depth' => 1.0, + 'preserveKeys' => false, + ], + [ + 1.1, + 1.2, + 2 + ] + ], + 'flatten array, and resets array keys, multiple depths' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => [ + 'a1.1' => 1.1, + 'a1.2' => 1.2 + ], + 'a2' => 2 + ], + 'b' => 3, + ], + 'preserveKeys' => false, + ], + [ + 1.1, + 1.2, + 2, + 3 + ] + ], + 'flatten array, one depth, not enough' => [ + 'flatten', + [ + 'array' => [ + 'a' => [ + 'a1' => [ + 'a1.1' => 1.1, + 'a1.2' => 1.2 + ], + 'a2' => 2 + ], + 'b' => 3, + ], + 'depth' => 1, + 'preserveKeys' => false, + ], + [ + [ + 'a1.1' => 1.1, + 'a1.2' => 1.2 + ], + 2, + 3 + ] + ] + ]; + } } diff --git a/tests/Unit/Support/CollectionTest.php b/tests/Unit/Support/CollectionTest.php new file mode 100644 index 0000000..9039364 --- /dev/null +++ b/tests/Unit/Support/CollectionTest.php @@ -0,0 +1,238 @@ + $collection + * @param array $functionParams + */ + #[Test] + #[DataProvider('push_function_provider')] + #[DataProvider('put_function_provider')] + #[DataProvider('first_function_provider')] + #[DataProvider('map_function_provider')] + #[DataProvider('collapse_function_provider')] + #[DataProvider('flatten_function_provider')] + #[DataProvider('all_function_provider')] + #[DataProvider('validation_functions_provider')] + public function will_be_able_to_perform_functions( + Collection $collection, + string $functionName, + array $functionParams, + mixed $expected + ): void { + $result = $collection->{$functionName}(...$functionParams); + + if ($expected instanceof Collection) { + self::assertInstanceOf(Collection::class, $result); + self::assertCollection($result, $expected); + } else { + self::assertEquals($expected, $result); + } + } + + /** + * @return array + */ + public static function push_function_provider(): array + { + return [ + 'push' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'push', + 'functionParams' => [ 'item' => 4 ], + 'expected' => new Collection([1, 2, 3, 4]) + ] + ]; + } + + /** + * @return array + */ + public static function put_function_provider(): array + { + return [ + 'put in new key' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'put', + 'functionParams' => [ 'key' => 3, 'value' => 4 ], + 'expected' => new Collection([1, 2, 3, 4]) + ], + 'put in existing key will override value' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'put', + 'functionParams' => [ 'key' => 0, 'value' => 4 ], + 'expected' => new Collection([4, 2, 3]) + ] + ]; + } + + /** + * @return array + */ + public static function first_function_provider(): array + { + return [ + 'first without callable and non empty collection' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'first', + 'functionParams' => [], + 'expected' => 1 + ], + 'first without callable and empty collection' => [ + 'collection' => new Collection([]), + 'functionName' => 'first', + 'functionParams' => [], + 'expected' => null + ], + 'first with callable and existing item that will meet requirements' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'first', + 'functionParams' => [ 'callable' => static fn (int $item) => $item === 2 ], + 'expected' => 2 + ], + 'first with callable and no item that will meet requirements' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'first', + 'functionParams' => [ 'callable' => static fn (int $item) => $item === 4 ], + 'expected' => null + ] + ]; + } + + /** + * @return array + */ + public static function map_function_provider(): array + { + return [ + 'map' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'map', + 'functionParams' => [ 'callable' => static fn (int $item) => $item * 2 ], + 'expected' => new Collection([2, 4, 6]) + ] + ]; + } + + /** + * @return array + */ + public static function collapse_function_provider(): array + { + return [ + 'collapse' => [ + 'collection' => new Collection([ + new Collection([ 'a' => 1, 2, 3]), + new Collection([4, 5, 6]), + new Collection([7, 8, 9]) + ]), + 'functionName' => 'collapse', + 'functionParams' => [], + 'expected' => new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9]) + ], + + ]; + } + + /** + * @return array + */ + public static function flatten_function_provider(): array + { + return [ + 'flatten' => [ + 'collection' => new Collection([ + 'a1' => new Collection([ + 'a1.1' => 1.1, + 'a1.2' => new Collection([ + 'a1.2.1' => 1.21, + 'a1.2.2' => 1.22, + 'a1.2.3' => new Collection([ + 'a1.2.3.1' => 1.231, + 'a1.2.3.2' => 1.232, + 'a1.2.3.3' => 1.233 + ]) + ]) + ]) + ]), + 'functionName' => 'flatten', + 'functionParams' => [], + 'expected' => new Collection([ + 'a1.1' => 1.1, + 'a1.2.1' => 1.21, + 'a1.2.2' => 1.22, + 'a1.2.3.1' => 1.231, + 'a1.2.3.2' => 1.232, + 'a1.2.3.3' => 1.233 + ]) + ] + ]; + } + + /** + * @return array + */ + public static function all_function_provider(): array + { + return [ + 'all' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'all', + 'functionParams' => [], + 'expected' => [1, 2, 3] + ] + ]; + } + + /** + * @return array + */ + public static function validation_functions_provider(): array + { + return [ + 'isEmpty' => [ + 'collection' => new Collection([]), + 'functionName' => 'isEmpty', + 'functionParams' => [], + 'expected' => true + ], + 'isNotEmpty' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'isNotEmpty', + 'functionParams' => [], + 'expected' => true + ], + 'every' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'every', + 'functionParams' => [ 'callable' => static fn (int $item) => $item > 0 ], + 'expected' => true + ], + 'some' => [ + 'collection' => new Collection([1, 2, 3]), + 'functionName' => 'some', + 'functionParams' => [ 'callable' => static fn (int $item) => $item === 2 ], + 'expected' => true + ], + ]; + } + + /** + * @param Collection $collection + * @param Collection $expected + */ + private static function assertCollection(Collection $collection, Collection $expected): void + { + self::assertEquals($expected->all(), $collection->all()); + } +}