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());
+ }
+}