From 4bbfa120ae725a755b6a496d3168779da78545b8 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 15 Sep 2024 13:18:26 -0300 Subject: [PATCH 01/49] Ignore .DS_Store --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7ae6add..bd2b70e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor phpcs.xml phpunit.xml .phpunit.result.cache +.DS_Store From 171f458a7936c18b9779023e4488a1724d9a0a9b Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 15 Sep 2024 13:18:34 -0300 Subject: [PATCH 02/49] Update readme --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 853d5bb..3b0f8f7 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,13 @@ [![Build Status][ico-actions]][link-actions] [![Coverage Status][ico-scrutinizer]][link-scrutinizer] [![Quality Score][ico-code-quality]][link-code-quality] +[![PHPStan Level][ico-phpstan]][link-phpstan] [![Latest Version][ico-version]][link-packagist] [![Software License][ico-license]](LICENSE.md) -[![PSR-12][ico-psr12]][link-psr12] +[![PER][ico-per]][link-per] [![Total Downloads][ico-downloads]][link-downloads] -Zero-dependencies PHP library to supercharge enum functionalities. Similar libraries worth mentioning are: -- [Enum Helper](https://github.com/datomatic/enum-helper) by [Alberto Peripolli](https://github.com/trippo) -- [Enums](https://github.com/archtechx/enums) by [Samuel ล tancl](https://github.com/stancl) +Zero-dependencies PHP library to supercharge enum functionalities. ## ๐Ÿ“ฆ Install @@ -404,19 +403,21 @@ The MIT License (MIT). Please see [License File](LICENSE.md) for more informatio [ico-author]: https://img.shields.io/static/v1?label=author&message=cerbero90&color=50ABF1&logo=twitter&style=flat-square [ico-php]: https://img.shields.io/packagist/php-v/cerbero/enum?color=%234F5B93&logo=php&style=flat-square [ico-version]: https://img.shields.io/packagist/v/cerbero/enum.svg?label=version&style=flat-square -[ico-actions]: https://img.shields.io/github/workflow/status/cerbero90/enum/build?style=flat-square&logo=github +[ico-actions]: https://img.shields.io/github/actions/workflow/status/cerbero90/enum/build.yml?branch=master&style=flat-square&logo=github [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square -[ico-psr12]: https://img.shields.io/static/v1?label=compliance&message=PSR-12&color=blue&style=flat-square +[ico-per]: https://img.shields.io/static/v1?label=compliance&message=PER&color=blue&style=flat-square [ico-scrutinizer]: https://img.shields.io/scrutinizer/coverage/g/cerbero90/enum.svg?style=flat-square&logo=scrutinizer [ico-code-quality]: https://img.shields.io/scrutinizer/g/cerbero90/enum.svg?style=flat-square&logo=scrutinizer +[ico-phpstan]: https://img.shields.io/badge/level-max-success?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAGb0lEQVR42u1Xe1BUZRS/y4Kg8oiR3FCCBUySESZBRCiaBnmEsOzeSzsg+KxYYO9dEEftNRqZjx40FRZkTpqmOz5S2LsXlEZBciatkQnHDGYaGdFy1EpGMHl/p/PdFlt2rk5O+J9n5nA/vtf5ned3lnlISpRhafBlLRLHCtJGVrB/ZBDsaw2lUqzReGAC46DstTYfnSCGUjaaDvgxACo6j3vUenNdImeRXqdnWV5az5rrnzeZznj8J+E5Ftsclhf3s4J4CS/oRx5Bvon8ZU65FGYQxAwcf85a7CeRz+C41THejueydCZ7AAK34nwv3kHP/oUKdOL4K7258fF7Cud427O48RQeGkIGJ77N8fZqlrcfRP4d/x90WQfHXLeBt9dTrSlwl3V65ynWLM1SEA2qbNQckbe4Xmww10Hmy3shid0CMcmlEJtSDsl5VZBdfAgMvI3uuR+moJqN6LaxmpsOBeLCDmTifCB92RcQmbAUJvtqALc5sQr8p86gYBCcFdBq9wOin7NQax6ewlB6rqLZHf23FP10y3lj6uJtEBg2HxiVCtzd3SEwMBCio6Nh9uzZ4O/vLwOZ4OUNM2NyIGPFrvuzBG//lRPs+VQ2k1ki+ePkd84bskz7YFpYgizEz88P8vPzYffu3dDS0gJNTU1QXV0NqampRK1WIwgfiE4qhOyig0rC+pCvK8QUoML7uJVHA5kcQUp3DSpqWjc3d/Dy8oKioiLo6uqCoaEhuHb1KvT09AAhBFpbW4lOpyMyyIBQSCmoUQLQzgniNvz+obB2HS2RwBgE6dOxCyJogmNkP2u1Wrhw4QJ03+iGrR9XEd3CTNBn6eCbo40wPDwMdXV1BF1DVG5qiEtboxSUP6J71+D3NwUAhLOIRQzm7lnnhYUv7QFv/yDZ/Lm5ubK2DVI9iZ8bR8JDtEB57lNzENQN6OjoIGlpabIVZsYaMTO+hrikRRA1JxmSX9hE7/sJtVyF38tKsUCVZxBhz9jI3wGT/QJlADzPAyXrnj0kInzGHQCRMyOg/ed2uHjxIuE4TgYQHq2DLJqumashY+lnsMC4GVC5do6XVuK9l+4SkN8y+GfYeVJn2g++U7QygPT0dBgYGIDvT58mnF5PQcjC83PzSF9fH7S1tZGEhAQZQOT8JaA317oIkM6jS8uVLSDzOQqg23Uh+MlkOf00Gg0cP34c+vv74URzM9n41gby/rvvkc7OThlATU3NCGYJUXt4QaLuTYwBcTSOBmj1RD7D4Tsix4ByOjZRF/zgupDEbgZ3j4ly/qekpND0o5aQ44HS4OAgsVqtI1gTZO01IbG0aP1bknnxCDUvArHi+B0lJSlzglTFYO2udF3Ql9TCrHn5oEIreHp6QlRUFJSUlJCqqipSWVlJ8vLyCGYIFS7HS3zGa87mv4lcjLwLlStlLTKYYUUAlvrlDGcW45wKxXX6aqHZNutM+1oQBHFTewAKkoH4+vqCj48PYAGS5yb5amjNoO+CU2SL53NKpDD0vxHHmOJir7L5xUvZgm0us2R142ScOIyVqYvlpWU4XoHIP8DXL2b+wjdWeXh6U2FjmIIKmbWAYPFRMus62h/geIvjOQYlpuDysQrLL6Ger49HgW8jqvXUhI7UvDb9iaSTDqHtyItiF5Suw5ewF/Nd8VJ6zlhsn06bEhwX4NyfCvuGEeRpTmh4mkG68yDpyuzB9EUcjU5awbAgncPlAeSdAQER0zCndzqVbeXC4qDsMpvGEYBXRnsDx4N3Auf1FCTjTIaVtY/QTmd0I8bBVm1kejEubUfO01vqImn3c49X7qpeqI9inIgtbpxK3YrKfIJCt+OeV2nfUVFR4ca4EkVENyA7gkYcMfB1R5MMmxZ7ez/2KF5SSN1yV+158UPsJT0ZBcI2bRLtIXGoYu5FerOUiJe1OfsL3XEWH43l2KS+iJF9+S4FpcNgsc+j8cT8H4o1bfPg/qkLt50uJ1RzdMsGg0UqwfEN114Pwb1CtWTGg+Y9U5ClK9x7xUWI7BI5VQVp0AVcQ3bZkQhmnEgdHhKyNSZe16crtBIlc7sIb6cRLft2PCgoKGjijBDtjrAQ7a3EdMsxzIRflAFIhPb6mHYmYwX+WBlPQgskhgVryyJCQyNyBLsBQdQ6fgsQhyt6MSOOsWZ7gbH8wETmgRKAijatNL8Ngm0xx4tLcsps0Wzx4al0jXlI40B/A3pa144MDtSgAAAAAElFTkSuQmCC [ico-downloads]: https://img.shields.io/packagist/dt/cerbero/enum.svg?style=flat-square [link-author]: https://twitter.com/cerbero90 [link-php]: https://www.php.net [link-packagist]: https://packagist.org/packages/cerbero/enum [link-actions]: https://github.com/cerbero90/enum/actions?query=workflow%3Abuild -[link-psr12]: https://www.php-fig.org/psr/psr-12/ +[link-per]: https://www.php-fig.org/per/coding-style/ [link-scrutinizer]: https://scrutinizer-ci.com/g/cerbero90/enum/code-structure [link-code-quality]: https://scrutinizer-ci.com/g/cerbero90/enum [link-downloads]: https://packagist.org/packages/cerbero/enum +[link-phpstan]: https://phpstan.org/ [link-contributors]: ../../contributors From ab7b9ca758f4cccecc988338b72678be26dfc403 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 16 Sep 2024 11:36:19 -0300 Subject: [PATCH 03/49] Lint --- composer.json | 12 +++++++----- duster.json | 13 +++++++++++++ phpstan-baseline.neon | 1 + phpstan.neon | 6 ++++++ pint.json | 19 +++++++++++++++++++ src/CasesCollection.php | 16 ++++++++-------- src/Concerns/Hydrates.php | 2 +- src/Concerns/KeysAware.php | 2 +- 8 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 duster.json create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon create mode 100644 pint.json diff --git a/composer.json b/composer.json index 4d39d58..bac7027 100644 --- a/composer.json +++ b/composer.json @@ -18,9 +18,11 @@ "php": "^8.1" }, "require-dev": { - "pestphp/pest": "^1.21", + "pestphp/pest": "^2.0", + "phpstan/phpstan": "^1.9", "scrutinizer/ocular": "^1.9", - "squizlabs/php_codesniffer": "^3.0" + "squizlabs/php_codesniffer": "^3.0", + "tightenco/duster": "^2.0" }, "autoload": { "psr-4": { @@ -33,9 +35,9 @@ } }, "scripts": { - "test": "pest", - "check-style": "phpcs --standard=PSR12 src", - "fix-style": "phpcbf --standard=PSR12 src" + "fix": "duster fix -u tlint,phpcodesniffer,pint", + "lint": "duster lint -u tlint,phpcodesniffer,pint,phpstan", + "test": "pest" }, "extra": { "branch-alias": { diff --git a/duster.json b/duster.json new file mode 100644 index 0000000..e3834c7 --- /dev/null +++ b/duster.json @@ -0,0 +1,13 @@ +{ + "include": [ + "src" + ], + "exclude": [ + "tests" + ], + "scripts": { + "lint": { + "phpstan": ["./vendor/bin/phpstan", "analyse"] + } + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1 @@ + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..5209c3e --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: max + paths: + - src +includes: + - phpstan-baseline.neon diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..03e092d --- /dev/null +++ b/pint.json @@ -0,0 +1,19 @@ +{ + "preset": "per", + "rules": { + "align_multiline_comment": true, + "combine_consecutive_issets": true, + "combine_consecutive_unsets": true, + "concat_space": {"spacing": "one"}, + "explicit_string_variable": true, + "ordered_imports": { + "sort_algorithm": "alpha", + "imports_order": [ + "class", + "function", + "const" + ] + }, + "simple_to_complex_string_variable": true + } +} diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 452f76e..2d34406 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -74,7 +74,7 @@ public function count(): int */ public function first(callable $callback = null, mixed $default = null): mixed { - $callback ??= fn () => true; + $callback ??= fn() => true; foreach ($this->cases as $case) { if ($callback($case)) { @@ -190,7 +190,7 @@ public function pluck(callable|string $value = null, callable|string $key = null */ public function filter(callable|string $filter): static { - $callback = is_callable($filter) ? $filter : fn (mixed $case) => $case->get($filter) === true; + $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->get($filter) === true; $cases = array_filter($this->cases, $callback); return new static(array_values($cases)); @@ -204,7 +204,7 @@ public function filter(callable|string $filter): static */ public function only(string ...$name): static { - return $this->filter(fn (UnitEnum $case) => in_array($case->name, $name)); + return $this->filter(fn(UnitEnum $case) => in_array($case->name, $name)); } /** @@ -215,7 +215,7 @@ public function only(string ...$name): static */ public function except(string ...$name): static { - return $this->filter(fn (UnitEnum $case) => !in_array($case->name, $name)); + return $this->filter(fn(UnitEnum $case) => !in_array($case->name, $name)); } /** @@ -226,7 +226,7 @@ public function except(string ...$name): static */ public function onlyValues(string|int ...$value): static { - return $this->filter(fn (UnitEnum $case) => $this->enumIsBacked && in_array($case->value, $value, true)); + return $this->filter(fn(UnitEnum $case) => $this->enumIsBacked && in_array($case->value, $value, true)); } /** @@ -237,7 +237,7 @@ public function onlyValues(string|int ...$value): static */ public function exceptValues(string|int ...$value): static { - return $this->filter(fn (UnitEnum $case) => $this->enumIsBacked && !in_array($case->value, $value, true)); + return $this->filter(fn(UnitEnum $case) => $this->enumIsBacked && !in_array($case->value, $value, true)); } /** @@ -270,7 +270,7 @@ public function sortBy(callable|string $key): static { $cases = $this->cases; - usort($cases, fn ($a, $b) => $a->get($key) <=> $b->get($key)); + usort($cases, fn($a, $b) => $a->get($key) <=> $b->get($key)); return new static($cases); } @@ -285,7 +285,7 @@ public function sortDescBy(callable|string $key): static { $cases = $this->cases; - usort($cases, fn ($a, $b) => $a->get($key) > $b->get($key) ? -1 : 1); + usort($cases, fn($a, $b) => $a->get($key) > $b->get($key) ? -1 : 1); return new static($cases); } diff --git a/src/Concerns/Hydrates.php b/src/Concerns/Hydrates.php index 2c664da..7abf09c 100644 --- a/src/Concerns/Hydrates.php +++ b/src/Concerns/Hydrates.php @@ -81,7 +81,7 @@ public static function fromKey(callable|string $key, mixed $value): CasesCollect return $result; } - $target = is_callable($key) ? 'given callable key' : "key \"$key\""; + $target = is_callable($key) ? 'given callable key' : "key \"{$key}\""; throw new ValueError(sprintf('Invalid value for the %s for enum "%s"', $target, static::class)); } diff --git a/src/Concerns/KeysAware.php b/src/Concerns/KeysAware.php index 73578e1..9a22205 100644 --- a/src/Concerns/KeysAware.php +++ b/src/Concerns/KeysAware.php @@ -23,7 +23,7 @@ public function get(callable|string $key): mixed try { return is_callable($key) ? $key($this) : ($this->$key ?? $this->$key()); } catch (Throwable) { - $target = is_callable($key) ? 'The given callable' : "\"$key\""; + $target = is_callable($key) ? 'The given callable' : "\"{$key}\""; throw new ValueError(sprintf('%s is not a valid key for enum "%s"', $target, static::class)); } } From b00f5ac3a1aa3573f84be93605026677db50cbc0 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 16 Sep 2024 14:02:52 -0300 Subject: [PATCH 04/49] Fix code style --- tests/BackedEnumTest.php | 56 +++++++++++++++++------------------ tests/CasesCollectionTest.php | 16 +++++----- tests/PureEnumTest.php | 56 +++++++++++++++++------------------ 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index 33d2772..f3f1532 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -38,7 +38,7 @@ ->toBe(['red' => BackedEnum::one, 'green' => BackedEnum::two, 'blue' => BackedEnum::three]); it('retrieves all cases keyed by the result of a closure') - ->expect(BackedEnum::casesBy(fn (BackedEnum $case) => $case->shape())) + ->expect(BackedEnum::casesBy(fn(BackedEnum $case) => $case->shape())) ->toBe(['triangle' => BackedEnum::one, 'square' => BackedEnum::two, 'circle' => BackedEnum::three]); it('retrieves all cases grouped by a custom key', function () { @@ -47,12 +47,12 @@ }); it('retrieves all cases grouped by the result of a closure', function () { - expect(BackedEnum::groupBy(fn (BackedEnum $case) => $case->isOdd())) + expect(BackedEnum::groupBy(fn(BackedEnum $case) => $case->isOdd())) ->toBe([1 => [BackedEnum::one, BackedEnum::three], 0 => [BackedEnum::two]]); }); it('retrieves a collection with the filtered cases') - ->expect(BackedEnum::filter(fn (UnitEnum $case) => $case->name !== 'three')) + ->expect(BackedEnum::filter(fn(UnitEnum $case) => $case->name !== 'three')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::one, BackedEnum::two]); @@ -101,11 +101,11 @@ ->toBe(['triangle' => 'red', 'square' => 'green', 'circle' => 'blue']); it('retrieves an associative array with keys and values resolved from closures') - ->expect(BackedEnum::pluck(fn (BackedEnum $case) => $case->name, fn (BackedEnum $case) => $case->color())) + ->expect(BackedEnum::pluck(fn(BackedEnum $case) => $case->name, fn(BackedEnum $case) => $case->color())) ->toBe(['red' => 'one', 'green' => 'two', 'blue' => 'three']); it('determines whether an enum has a target') - ->expect(fn (mixed $target, bool $result) => BackedEnum::has($target) === $result) + ->expect(fn(mixed $target, bool $result) => BackedEnum::has($target) === $result) ->toBeTrue() ->with([ [BackedEnum::one, true], @@ -119,7 +119,7 @@ ]); it('determines whether an enum does not have a target') - ->expect(fn (mixed $target, bool $result) => BackedEnum::doesntHave($target) === $result) + ->expect(fn(mixed $target, bool $result) => BackedEnum::doesntHave($target) === $result) ->toBeTrue() ->with([ [BackedEnum::one, false], @@ -133,7 +133,7 @@ ]); it('determines whether an enum case matches a target') - ->expect(fn (mixed $target, bool $result) => BackedEnum::one->is($target) === $result) + ->expect(fn(mixed $target, bool $result) => BackedEnum::one->is($target) === $result) ->toBeTrue() ->with([ [BackedEnum::one, true], @@ -147,7 +147,7 @@ ]); it('determines whether an enum case does not match a target') - ->expect(fn (mixed $target, bool $result) => BackedEnum::one->isNot($target) === $result) + ->expect(fn(mixed $target, bool $result) => BackedEnum::one->isNot($target) === $result) ->toBeTrue() ->with([ [BackedEnum::one, false], @@ -161,7 +161,7 @@ ]); it('determines whether an enum case matches some targets') - ->expect(fn (mixed $targets, bool $result) => BackedEnum::one->in($targets) === $result) + ->expect(fn(mixed $targets, bool $result) => BackedEnum::one->in($targets) === $result) ->toBeTrue() ->with([ [[BackedEnum::one, BackedEnum::two], true], @@ -175,7 +175,7 @@ ]); it('determines whether an enum case does not match any target') - ->expect(fn (mixed $targets, bool $result) => BackedEnum::one->notIn($targets) === $result) + ->expect(fn(mixed $targets, bool $result) => BackedEnum::one->notIn($targets) === $result) ->toBeTrue() ->with([ [[BackedEnum::one, BackedEnum::two], false], @@ -207,7 +207,7 @@ ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); it('retrieves a collection of cases sorted by value descending') - ->expect(BackedEnum::sortDescByValue()) + ->expect(BackedEnum::sortByDescValue()) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); @@ -219,19 +219,19 @@ ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); it('retrieves a collection of cases sorted by a custom value descending') - ->expect(BackedEnum::sortDescBy('color')) + ->expect(BackedEnum::sortByDesc('color')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); it('retrieves a collection of cases sorted by the result of a closure ascending') - ->expect(BackedEnum::sortBy(fn (BackedEnum $case) => $case->shape())) + ->expect(BackedEnum::sortBy(fn(BackedEnum $case) => $case->shape())) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); it('retrieves a collection of cases sorted by the result of a closure descending') - ->expect(BackedEnum::sortDescBy(fn (BackedEnum $case) => $case->shape())) + ->expect(BackedEnum::sortByDesc(fn(BackedEnum $case) => $case->shape())) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); @@ -241,7 +241,7 @@ ->toBe(3); it('retrieves the case hydrated from a value') - ->expect(fn (int $value, BackedEnum $case) => BackedEnum::from($value) === $case) + ->expect(fn(int $value, BackedEnum $case) => BackedEnum::from($value) === $case) ->toBeTrue() ->with([ [1, BackedEnum::one], @@ -249,11 +249,11 @@ [3, BackedEnum::three], ]); -it('throws a value error when hydrating backed cases with a missing value', fn () => BackedEnum::from(4)) +it('throws a value error when hydrating backed cases with a missing value', fn() => BackedEnum::from(4)) ->throws(ValueError::class, '4 is not a valid backing value for enum "Cerbero\Enum\BackedEnum"'); it('retrieves the case hydrated from a value or returns null') - ->expect(fn (int $value, ?BackedEnum $case) => BackedEnum::tryFrom($value) === $case) + ->expect(fn(int $value, ?BackedEnum $case) => BackedEnum::tryFrom($value) === $case) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ @@ -264,7 +264,7 @@ ]); it('retrieves the case hydrated from a name') - ->expect(fn (string $name, BackedEnum $case) => BackedEnum::fromName($name) === $case) + ->expect(fn(string $name, BackedEnum $case) => BackedEnum::fromName($name) === $case) ->toBeTrue() ->with([ ['one', BackedEnum::one], @@ -272,11 +272,11 @@ ['three', BackedEnum::three], ]); -it('throws a value error when hydrating backed cases with a missing name', fn () => BackedEnum::fromName('four')) +it('throws a value error when hydrating backed cases with a missing name', fn() => BackedEnum::fromName('four')) ->throws(ValueError::class, '"four" is not a valid name for enum "Cerbero\Enum\BackedEnum"'); it('retrieves the case hydrated from a name or returns null') - ->expect(fn (string $name, ?BackedEnum $case) => BackedEnum::tryFromName($name) === $case) + ->expect(fn(string $name, ?BackedEnum $case) => BackedEnum::tryFromName($name) === $case) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ @@ -287,7 +287,7 @@ ]); it('retrieves the cases hydrated from a key') - ->expect(fn (string $key, mixed $value, array $cases) => BackedEnum::fromKey($key, $value)->cases() === $cases) + ->expect(fn(string $key, mixed $value, array $cases) => BackedEnum::fromKey($key, $value)->cases() === $cases) ->toBeTrue() ->with([ ['color', 'red', [BackedEnum::one]], @@ -296,16 +296,16 @@ ]); it('retrieves the cases hydrated from a key using a closure') - ->expect(BackedEnum::fromKey(fn (BackedEnum $case) => $case->shape(), 'square')) + ->expect(BackedEnum::fromKey(fn(BackedEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::two]); -it('throws a value error when hydrating cases with an invalid key', fn () => BackedEnum::fromKey('color', 'orange')) +it('throws a value error when hydrating cases with an invalid key', fn() => BackedEnum::fromKey('color', 'orange')) ->throws(ValueError::class, 'Invalid value for the key "color" for enum "Cerbero\Enum\BackedEnum"'); it('retrieves the case hydrated from a key or returns null') - ->expect(fn (string $key, mixed $value, ?array $cases) => BackedEnum::tryFromKey($key, $value)?->cases() === $cases) + ->expect(fn(string $key, mixed $value, ?array $cases) => BackedEnum::tryFromKey($key, $value)?->cases() === $cases) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ @@ -316,13 +316,13 @@ ]); it('attempts to retrieve the case hydrated from a key using a closure') - ->expect(BackedEnum::tryFromKey(fn (BackedEnum $case) => $case->shape(), 'square')) + ->expect(BackedEnum::tryFromKey(fn(BackedEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::two]); it('retrieves the key of a case') - ->expect(fn (string $key, mixed $value) => BackedEnum::one->get($key) === $value) + ->expect(fn(string $key, mixed $value) => BackedEnum::one->get($key) === $value) ->toBeTrue() ->with([ ['name', 'one'], @@ -332,8 +332,8 @@ ]); it('retrieves the key of a case using a closure') - ->expect(BackedEnum::one->get(fn (BackedEnum $case) => $case->color())) + ->expect(BackedEnum::one->get(fn(BackedEnum $case) => $case->color())) ->toBe('red'); -it('throws a value error when attempting to retrieve an invalid key', fn () => BackedEnum::one->get('invalid')) +it('throws a value error when attempting to retrieve an invalid key', fn() => BackedEnum::one->get('invalid')) ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\BackedEnum"'); diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php index f22c519..573b232 100644 --- a/tests/CasesCollectionTest.php +++ b/tests/CasesCollectionTest.php @@ -21,7 +21,7 @@ it('retrieves the first case with a closure') ->expect(new CasesCollection(PureEnum::cases())) - ->first(fn (PureEnum $case) => !$case->isOdd()) + ->first(fn(PureEnum $case) => !$case->isOdd()) ->toBe(PureEnum::two); it('returns null if no case is present') @@ -46,7 +46,7 @@ it('retrieves the cases keyed by a custom closure') ->expect(new CasesCollection(PureEnum::cases())) - ->keyBy(fn (PureEnum $case) => $case->shape()) + ->keyBy(fn(PureEnum $case) => $case->shape()) ->toBe(['triangle' => PureEnum::one, 'square' => PureEnum::two, 'circle' => PureEnum::three]); it('retrieves the cases keyed by value') @@ -66,7 +66,7 @@ it('retrieves the cases grouped by a custom closure') ->expect(new CasesCollection(PureEnum::cases())) - ->groupBy(fn (PureEnum $case) => $case->isOdd()) + ->groupBy(fn(PureEnum $case) => $case->isOdd()) ->toBe([1 => [PureEnum::one, PureEnum::three], 0 => [PureEnum::two]]); it('retrieves all the names of the cases') @@ -101,7 +101,7 @@ it('retrieves a list of custom values when plucking with a closure') ->expect(new CasesCollection(PureEnum::cases())) - ->pluck(fn (PureEnum $case) => $case->shape()) + ->pluck(fn(PureEnum $case) => $case->shape()) ->toBe(['triangle', 'square', 'circle']); it('retrieves an associative array with custom values and keys when plucking with arguments') @@ -111,11 +111,11 @@ it('retrieves an associative array with custom values and keys when plucking with closures') ->expect(new CasesCollection(PureEnum::cases())) - ->pluck(fn (PureEnum $case) => $case->shape(), fn (PureEnum $case) => $case->color()) + ->pluck(fn(PureEnum $case) => $case->shape(), fn(PureEnum $case) => $case->color()) ->toBe(['red' => 'triangle', 'green' => 'square', 'blue' => 'circle']); it('retrieves a collection with filtered cases', function () { - expect((new CasesCollection(PureEnum::cases()))->filter(fn (PureEnum $case) => $case->isOdd())) + expect((new CasesCollection(PureEnum::cases()))->filter(fn(PureEnum $case) => $case->isOdd())) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::three]); @@ -192,7 +192,7 @@ }); it('retrieves a collection of cases sorted by a key decending', function () { - expect((new CasesCollection(PureEnum::cases()))->sortDescBy('color')) + expect((new CasesCollection(PureEnum::cases()))->sortByDesc('color')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); @@ -206,7 +206,7 @@ }); it('retrieves a collection of cases sorted by value decending', function () { - expect((new CasesCollection(BackedEnum::cases()))->sortDescByValue()) + expect((new CasesCollection(BackedEnum::cases()))->sortByDescValue()) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index b52341b..63005ef 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -33,7 +33,7 @@ }); it('retrieves all cases keyed by the result of a closure', function () { - expect(PureEnum::casesBy(fn (PureEnum $case) => $case->shape())) + expect(PureEnum::casesBy(fn(PureEnum $case) => $case->shape())) ->toBe(['triangle' => PureEnum::one, 'square' => PureEnum::two, 'circle' => PureEnum::three]); }); @@ -43,7 +43,7 @@ }); it('retrieves all cases grouped by the result of a closure', function () { - expect(PureEnum::groupBy(fn (PureEnum $case) => $case->isOdd())) + expect(PureEnum::groupBy(fn(PureEnum $case) => $case->isOdd())) ->toBe([1 => [PureEnum::one, PureEnum::three], 0 => [PureEnum::two]]); }); @@ -56,7 +56,7 @@ }); it('retrieves a collection with the filtered cases', function () { - expect(PureEnum::filter(fn (UnitEnum $case) => $case->name !== 'three')) + expect(PureEnum::filter(fn(UnitEnum $case) => $case->name !== 'three')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::two]); @@ -111,12 +111,12 @@ }); it('retrieves an associative array with keys and values resolved from closures', function () { - expect(PureEnum::pluck(fn (PureEnum $case) => $case->name, fn (PureEnum $case) => $case->color())) + expect(PureEnum::pluck(fn(PureEnum $case) => $case->name, fn(PureEnum $case) => $case->color())) ->toBe(['red' => 'one', 'green' => 'two', 'blue' => 'three']); }); it('determines whether an enum has a target') - ->expect(fn (mixed $target, bool $result) => PureEnum::has($target) === $result) + ->expect(fn(mixed $target, bool $result) => PureEnum::has($target) === $result) ->toBeTrue() ->with([ [PureEnum::one, true], @@ -130,7 +130,7 @@ ]); it('determines whether an enum does not have a target') - ->expect(fn (mixed $target, bool $result) => PureEnum::doesntHave($target) === $result) + ->expect(fn(mixed $target, bool $result) => PureEnum::doesntHave($target) === $result) ->toBeTrue() ->with([ [PureEnum::one, false], @@ -144,7 +144,7 @@ ]); it('determines whether an enum case matches a target') - ->expect(fn (mixed $target, bool $result) => PureEnum::one->is($target) === $result) + ->expect(fn(mixed $target, bool $result) => PureEnum::one->is($target) === $result) ->toBeTrue() ->with([ [PureEnum::one, true], @@ -158,7 +158,7 @@ ]); it('determines whether an enum case does not match a target') - ->expect(fn (mixed $target, bool $result) => PureEnum::one->isNot($target) === $result) + ->expect(fn(mixed $target, bool $result) => PureEnum::one->isNot($target) === $result) ->toBeTrue() ->with([ [PureEnum::one, false], @@ -172,7 +172,7 @@ ]); it('determines whether an enum case matches some targets') - ->expect(fn (mixed $targets, bool $result) => PureEnum::one->in($targets) === $result) + ->expect(fn(mixed $targets, bool $result) => PureEnum::one->in($targets) === $result) ->toBeTrue() ->with([ [[PureEnum::one, PureEnum::two], true], @@ -186,7 +186,7 @@ ]); it('determines whether an enum case does not match any target') - ->expect(fn (mixed $targets, bool $result) => PureEnum::one->notIn($targets) === $result) + ->expect(fn(mixed $targets, bool $result) => PureEnum::one->notIn($targets) === $result) ->toBeTrue() ->with([ [[PureEnum::one, PureEnum::two], false], @@ -221,7 +221,7 @@ }); it('retrieves a collection of cases sorted by value descending', function () { - expect(PureEnum::sortDescByValue()) + expect(PureEnum::sortByDescValue()) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBeEmpty(); @@ -235,21 +235,21 @@ }); it('retrieves a collection of cases sorted by a custom value descending', function () { - expect(PureEnum::sortDescBy('color')) + expect(PureEnum::sortByDesc('color')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); }); it('retrieves a collection of cases sorted by the result of a closure ascending', function () { - expect(PureEnum::sortBy(fn (PureEnum $case) => $case->shape())) + expect(PureEnum::sortBy(fn(PureEnum $case) => $case->shape())) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::three, PureEnum::two, PureEnum::one]); }); it('retrieves a collection of cases sorted by the result of a closure descending', function () { - expect(PureEnum::sortDescBy(fn (PureEnum $case) => $case->shape())) + expect(PureEnum::sortByDesc(fn(PureEnum $case) => $case->shape())) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); @@ -260,7 +260,7 @@ }); it('retrieves the case hydrated from a value') - ->expect(fn (string $value, PureEnum $case) => PureEnum::from($value) === $case) + ->expect(fn(string $value, PureEnum $case) => PureEnum::from($value) === $case) ->toBeTrue() ->with([ ['one', PureEnum::one], @@ -268,11 +268,11 @@ ['three', PureEnum::three], ]); -it('throws a value error when hydrating cases with an invalid value', fn () => PureEnum::from('1')) +it('throws a value error when hydrating cases with an invalid value', fn() => PureEnum::from('1')) ->throws(ValueError::class, '"1" is not a valid name for enum "Cerbero\Enum\PureEnum"'); it('retrieves the case hydrated from a value or returns null') - ->expect(fn (string $value, ?PureEnum $case) => PureEnum::tryFrom($value) === $case) + ->expect(fn(string $value, ?PureEnum $case) => PureEnum::tryFrom($value) === $case) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ @@ -283,7 +283,7 @@ ]); it('retrieves the case hydrated from a name') - ->expect(fn (string $name, PureEnum $case) => PureEnum::fromName($name) === $case) + ->expect(fn(string $name, PureEnum $case) => PureEnum::fromName($name) === $case) ->toBeTrue() ->with([ ['one', PureEnum::one], @@ -291,11 +291,11 @@ ['three', PureEnum::three], ]); -it('throws a value error when hydrating cases with an invalid name', fn () => PureEnum::fromName('1')) +it('throws a value error when hydrating cases with an invalid name', fn() => PureEnum::fromName('1')) ->throws(ValueError::class, '"1" is not a valid name for enum "Cerbero\Enum\PureEnum"'); it('retrieves the case hydrated from a name or returns null') - ->expect(fn (string $name, ?PureEnum $case) => PureEnum::tryFromName($name) === $case) + ->expect(fn(string $name, ?PureEnum $case) => PureEnum::tryFromName($name) === $case) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ @@ -306,7 +306,7 @@ ]); it('retrieves the cases hydrated from a key') - ->expect(fn (string $key, mixed $value, array $cases) => PureEnum::fromKey($key, $value)->cases() === $cases) + ->expect(fn(string $key, mixed $value, array $cases) => PureEnum::fromKey($key, $value)->cases() === $cases) ->toBeTrue() ->with([ ['color', 'red', [PureEnum::one]], @@ -315,16 +315,16 @@ ]); it('retrieves the cases hydrated from a key using a closure') - ->expect(PureEnum::fromKey(fn (PureEnum $case) => $case->shape(), 'square')) + ->expect(PureEnum::fromKey(fn(PureEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::two]); -it('throws a value error when hydrating cases with an invalid key', fn () => PureEnum::fromKey('color', 'orange')) +it('throws a value error when hydrating cases with an invalid key', fn() => PureEnum::fromKey('color', 'orange')) ->throws(ValueError::class, 'Invalid value for the key "color" for enum "Cerbero\Enum\PureEnum"'); it('retrieves the case hydrated from a key or returns null') - ->expect(fn (string $key, mixed $value, ?array $cases) => PureEnum::tryFromKey($key, $value)?->cases() === $cases) + ->expect(fn(string $key, mixed $value, ?array $cases) => PureEnum::tryFromKey($key, $value)?->cases() === $cases) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ @@ -335,13 +335,13 @@ ]); it('attempts to retrieve the case hydrated from a key using a closure') - ->expect(PureEnum::tryFromKey(fn (PureEnum $case) => $case->shape(), 'square')) + ->expect(PureEnum::tryFromKey(fn(PureEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::two]); it('retrieves the key of a case') - ->expect(fn (string $key, mixed $value) => PureEnum::one->get($key) === $value) + ->expect(fn(string $key, mixed $value) => PureEnum::one->get($key) === $value) ->toBeTrue() ->with([ ['name', 'one'], @@ -350,8 +350,8 @@ ]); it('retrieves the key of a case using a closure') - ->expect(PureEnum::one->get(fn (PureEnum $case) => $case->color())) + ->expect(PureEnum::one->get(fn(PureEnum $case) => $case->color())) ->toBe('red'); -it('throws a value error when attempting to retrieve an invalid key', fn () => PureEnum::one->get('invalid')) +it('throws a value error when attempting to retrieve an invalid key', fn() => PureEnum::one->get('invalid')) ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); From 8ea4a679198d8a0e5e60030bfb970a24c1fe344b Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 16 Sep 2024 14:03:01 -0300 Subject: [PATCH 05/49] Rename methods --- src/Concerns/CollectsCases.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Concerns/CollectsCases.php b/src/Concerns/CollectsCases.php index de50bd0..f5d30b3 100644 --- a/src/Concerns/CollectsCases.php +++ b/src/Concerns/CollectsCases.php @@ -194,9 +194,9 @@ public static function sortByValue(): CasesCollection * * @return CasesCollection */ - public static function sortDescByValue(): CasesCollection + public static function sortByDescValue(): CasesCollection { - return static::collect()->sortDescByValue(); + return static::collect()->sortByDescValue(); } /** @@ -216,8 +216,8 @@ public static function sortBy(callable|string $key): CasesCollection * @param callable|string $key * @return CasesCollection */ - public static function sortDescBy(callable|string $key): CasesCollection + public static function sortByDesc(callable|string $key): CasesCollection { - return static::collect()->sortDescBy($key); + return static::collect()->sortByDesc($key); } } From 4353867f109d7e1e4c433e6ca4ffb5205e416d89 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 16 Sep 2024 14:06:06 -0300 Subject: [PATCH 06/49] Add generics --- src/CasesCollection.php | 183 ++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 100 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 2d34406..b89629b 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -11,44 +11,42 @@ /** * The collection of enum cases. * + * @template TKey of array-key + * @template-covariant TValue of UnitEnum|BackedEnum + * + * @implements IteratorAggregate */ class CasesCollection implements Countable, IteratorAggregate { /** - * Whether the cases belong to a backed enum - * - * @var bool + * Whether the cases belong to a backed enum. */ - protected bool $enumIsBacked; + protected readonly bool $enumIsBacked; /** - * Instantiate the class + * Instantiate the class. * - * @param array $cases + * @param array $cases */ - public function __construct(protected array $cases) + final public function __construct(protected array $cases) { $this->enumIsBacked = $this->first() instanceof BackedEnum; } /** - * Retrieve the iterable cases + * Retrieve the iterable cases. * - * @return Traversable + * @return Traversable */ public function getIterator(): Traversable { - return (function () { - foreach ($this->cases as $case) { - yield $case; - } - })(); + yield from $this->cases; } /** - * Retrieve the cases + * Retrieve the cases. * - * @return array + * @return array */ public function cases(): array { @@ -56,9 +54,7 @@ public function cases(): array } /** - * Retrieve the count of cases - * - * @return int + * Retrieve the count of cases. */ public function count(): int { @@ -66,18 +62,20 @@ public function count(): int } /** - * Retrieve the first case + * Retrieve the first case. * - * @param callable|null $callback - * @param mixed $default - * @return mixed + * @template TFirstDefault + * + * @param (callable(TValue, TKey): bool)|null $callback + * @param TFirstDefault $default + * @return TValue|TFirstDefault */ public function first(callable $callback = null, mixed $default = null): mixed { $callback ??= fn() => true; - foreach ($this->cases as $case) { - if ($callback($case)) { + foreach ($this->cases as $key => $case) { + if ($callback($case, $key)) { return $case; } } @@ -86,9 +84,9 @@ public function first(callable $callback = null, mixed $default = null): mixed } /** - * Retrieve the cases keyed by name + * Retrieve the cases keyed by their own name. * - * @return array + * @return array */ public function keyByName(): array { @@ -96,26 +94,26 @@ public function keyByName(): array } /** - * Retrieve the cases keyed by the given key + * Retrieve the cases keyed by the given key. * - * @param callable|string $key - * @return array + * @param (callable(TValue): array-key)|string $key + * @return array */ public function keyBy(callable|string $key): array { $result = []; foreach ($this->cases as $case) { - $result[$case->get($key)] = $case; + $result[$case->get($key)] = $case; // @phpstan-ignore method.notFound } return $result; } /** - * Retrieve the cases keyed by value + * Retrieve the cases keyed by their own value. * - * @return array + * @return array */ public function keyByValue(): array { @@ -123,26 +121,26 @@ public function keyByValue(): array } /** - * Retrieve the cases grouped by the given key + * Retrieve the cases grouped by the given key. * - * @param callable|string $key - * @return array + * @param (callable(TValue): array-key)|string $key + * @return array */ public function groupBy(callable|string $key): array { $result = []; foreach ($this->cases as $case) { - $result[$case->get($key)][] = $case; + $result[$case->get($key)][] = $case; // @phpstan-ignore method.notFound } return $result; } /** - * Retrieve all the names of the cases + * Retrieve all the names of the cases. * - * @return array + * @return string[] */ public function names(): array { @@ -150,9 +148,9 @@ public function names(): array } /** - * Retrieve all the values of the backed cases + * Retrieve all the values of the backed cases. * - * @return array + * @return list */ public function values(): array { @@ -160,11 +158,13 @@ public function values(): array } /** - * Retrieve an array of values optionally keyed by the given key + * Retrieve an array of values optionally keyed by the given key. + * + * @template TPluckValue * - * @param callable|string|null $value - * @param callable|string|null $key - * @return array + * @param (callable(TValue): array-key)|string|null $value + * @param (callable(TValue): TPluckValue)|string|null $key + * @return array */ public function pluck(callable|string $value = null, callable|string $key = null): array { @@ -173,9 +173,9 @@ public function pluck(callable|string $value = null, callable|string $key = null foreach ($this->cases as $case) { if ($key === null) { - $result[] = $case->get($value); + $result[] = $case->get($value); // @phpstan-ignore method.notFound } else { - $result[$case->get($key)] = $case->get($value); + $result[$case->get($key)] = $case->get($value); // @phpstan-ignore-line } } @@ -183,24 +183,20 @@ public function pluck(callable|string $value = null, callable|string $key = null } /** - * Retrieve a collection with the filtered cases + * Retrieve a new collection with the filtered cases. * - * @param callable|string $filter - * @return static + * @param (callable(TValue): bool)|string $filter */ public function filter(callable|string $filter): static { + // @phpstan-ignore-next-line $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->get($filter) === true; - $cases = array_filter($this->cases, $callback); - return new static(array_values($cases)); + return new static(array_filter($this->cases, $callback)); } /** - * Retrieve a collection of cases having the given names - * - * @param string ...$name - * @return static + * Retrieve a new collection of cases having only the given names. */ public function only(string ...$name): static { @@ -208,10 +204,7 @@ public function only(string ...$name): static } /** - * Retrieve a collection of cases not having the given names - * - * @param string ...$name - * @return static + * Retrieve a collection of cases not having the given names. */ public function except(string ...$name): static { @@ -219,31 +212,29 @@ public function except(string ...$name): static } /** - * Retrieve a collection of backed cases having the given values - * - * @param string|int ...$value - * @return static + * Retrieve a new collection of backed cases having only the given values. */ public function onlyValues(string|int ...$value): static { - return $this->filter(fn(UnitEnum $case) => $this->enumIsBacked && in_array($case->value, $value, true)); + return $this->filter(function (UnitEnum $case) use ($value) { + /** @var BackedEnum $case */ + return $this->enumIsBacked && in_array($case->value, $value, true); + }); } /** - * Retrieve a collection of backed cases not having the given values - * - * @param string|int ...$value - * @return static + * Retrieve a new collection of backed cases not having the given values. */ public function exceptValues(string|int ...$value): static { - return $this->filter(fn(UnitEnum $case) => $this->enumIsBacked && !in_array($case->value, $value, true)); + return $this->filter(function (UnitEnum $case) use ($value) { + /** @var BackedEnum $case */ + return $this->enumIsBacked && !in_array($case->value, $value, true); + }); } /** - * Retrieve a collection of cases sorted by name ascending - * - * @return static + * Retrieve a new collection of cases sorted by their own name ascending. */ public function sort(): static { @@ -251,49 +242,43 @@ public function sort(): static } /** - * Retrieve a collection of cases sorted by name descending + * Retrieve a new collection of cases sorted by the given key ascending. * - * @return static - */ - public function sortDesc(): static - { - return $this->sortDescBy('name'); - } - - /** - * Retrieve a collection of cases sorted by the given key ascending - * - * @param callable|string $key - * @return static + * @param (callable(TValue): mixed)|string $key */ public function sortBy(callable|string $key): static { $cases = $this->cases; - usort($cases, fn($a, $b) => $a->get($key) <=> $b->get($key)); + uasort($cases, fn(mixed $a, mixed $b) => $a->get($key) <=> $b->get($key)); // @phpstan-ignore-line return new static($cases); } /** - * Retrieve a collection of cases sorted by the given key descending + * Retrieve a new collection of cases sorted by their own name descending. + */ + public function sortDesc(): static + { + return $this->sortByDesc('name'); + } + + /** + * Retrieve a new collection of cases sorted by the given key descending. * - * @param callable|string $key - * @return static + * @param (callable(TValue): mixed)|string $key */ - public function sortDescBy(callable|string $key): static + public function sortByDesc(callable|string $key): static { $cases = $this->cases; - usort($cases, fn($a, $b) => $a->get($key) > $b->get($key) ? -1 : 1); + uasort($cases, fn(mixed $a, mixed $b) => $a->get($key) > $b->get($key) ? -1 : 1); // @phpstan-ignore-line return new static($cases); } /** - * Retrieve a collection of cases sorted by value ascending - * - * @return static + * Retrieve a new collection of cases sorted by their own value ascending. */ public function sortByValue(): static { @@ -301,12 +286,10 @@ public function sortByValue(): static } /** - * Retrieve a collection of cases sorted by value descending - * - * @return static + * Retrieve a new collection of cases sorted by their own value descending. */ - public function sortDescByValue(): static + public function sortByDescValue(): static { - return $this->enumIsBacked ? $this->sortDescBy('value') : new static([]); + return $this->enumIsBacked ? $this->sortByDesc('value') : new static([]); } } From c6333fc08e5e9d1a2bcf99ac39521997fd4a099f Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 16 Sep 2024 14:06:24 -0300 Subject: [PATCH 07/49] Fix code style --- README.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 3b0f8f7..0ca7ec7 100644 --- a/README.md +++ b/README.md @@ -157,12 +157,12 @@ The keys defined in this enum are `name`, `value` (as it is a backed enum), `col PureEnum::one->get('name'); // 'one' PureEnum::one->get('value'); // throws ValueError as it is a pure enum PureEnum::one->get('color'); // 'red' -PureEnum::one->get(fn (PureEnum $caseOne) => $caseOne->isOdd()); // true +PureEnum::one->get(fn(PureEnum $caseOne) => $caseOne->isOdd()); // true BackedEnum::one->get('name'); // 'one' BackedEnum::one->get('value'); // 1 BackedEnum::one->get('color'); // 'red' -BackedEnum::one->get(fn (BackedEnum $caseOne) => $caseOne->isOdd()); // true +BackedEnum::one->get(fn(BackedEnum $caseOne) => $caseOne->isOdd()); // true ``` At first glance this method may seem an overkill as "keys" can be accessed directly by cases like this: @@ -193,11 +193,11 @@ PureEnum::tryFromName('four'); // null PureEnum::fromKey('name', 'one'); // CasesCollection PureEnum::fromKey('value', 1); // throws ValueError PureEnum::fromKey('color', 'red'); // CasesCollection -PureEnum::fromKey(fn (PureEnum $case) => $case->isOdd(), true); // CasesCollection +PureEnum::fromKey(fn(PureEnum $case) => $case->isOdd(), true); // CasesCollection PureEnum::tryFromKey('name', 'one'); // CasesCollection PureEnum::tryFromKey('value', 1); // null PureEnum::tryFromKey('color', 'red'); // CasesCollection -PureEnum::tryFromKey(fn (PureEnum $case) => $case->isOdd(), true); // CasesCollection +PureEnum::tryFromKey(fn(PureEnum $case) => $case->isOdd(), true); // CasesCollection BackedEnum::from(1); // BackedEnum::one BackedEnum::from('1'); // throws ValueError @@ -210,11 +210,11 @@ BackedEnum::tryFromName('four'); // null BackedEnum::fromKey('name', 'one'); // CasesCollection BackedEnum::fromKey('value', 1); // CasesCollection BackedEnum::fromKey('color', 'red'); // CasesCollection -BackedEnum::fromKey(fn (BackedEnum $case) => $case->isOdd(), true); // CasesCollection +BackedEnum::fromKey(fn(BackedEnum $case) => $case->isOdd(), true); // CasesCollection BackedEnum::tryFromKey('name', 'one'); // CasesCollection BackedEnum::tryFromKey('value', 1); // CasesCollection BackedEnum::tryFromKey('color', 'red'); // CasesCollection -BackedEnum::tryFromKey(fn (BackedEnum $case) => $case->isOdd(), true); // CasesCollection +BackedEnum::tryFromKey(fn(BackedEnum $case) => $case->isOdd(), true); // CasesCollection ``` While pure enums try to hydrate cases from names, backed enums can hydrate from both names and values. Even keys can be used to hydrate cases, cases are then wrapped into a [`CasesCollection`](#cases-collection) to allow further processing. @@ -235,11 +235,11 @@ PureEnum::names(); // ['one', 'two', 'three'] PureEnum::values(); // [] PureEnum::pluck(); // ['one', 'two', 'three'] PureEnum::pluck('color'); // ['red', 'green', 'blue'] -PureEnum::pluck(fn (PureEnum $case) => $case->isOdd()); // [true, false, true] +PureEnum::pluck(fn(PureEnum $case) => $case->isOdd()); // [true, false, true] PureEnum::pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] -PureEnum::pluck(fn (PureEnum $case) => $case->isOdd(), fn (PureEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] +PureEnum::pluck(fn(PureEnum $case) => $case->isOdd(), fn(PureEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] PureEnum::filter('isOdd'); // CasesCollection -PureEnum::filter(fn (PureEnum $case) => $case->isOdd()); // CasesCollection +PureEnum::filter(fn(PureEnum $case) => $case->isOdd()); // CasesCollection PureEnum::only('two', 'three'); // CasesCollection PureEnum::except('two', 'three'); // CasesCollection PureEnum::onlyValues(2, 3); // CasesCollection<> @@ -247,9 +247,9 @@ PureEnum::exceptValues(2, 3); // CasesCollection<> PureEnum::sort(); // CasesCollection PureEnum::sortDesc(); // CasesCollection PureEnum::sortByValue(); // CasesCollection<> -PureEnum::sortDescByValue(); // CasesCollection<> +PureEnum::sortByDescValue(); // CasesCollection<> PureEnum::sortBy('color'); // CasesCollection -PureEnum::sortDescBy(fn (PureEnum $case) => $case->color()); // CasesCollection +PureEnum::sortByDesc(fn(PureEnum $case) => $case->color()); // CasesCollection BackedEnum::collect(); // CasesCollection BackedEnum::count(); // 3 @@ -261,11 +261,11 @@ BackedEnum::names(); // ['one', 'two', 'three'] BackedEnum::values(); // [1, 2, 3] BackedEnum::pluck(); // [1, 2, 3] BackedEnum::pluck('color'); // ['red', 'green', 'blue'] -BackedEnum::pluck(fn (BackedEnum $case) => $case->isOdd()); // [true, false, true] +BackedEnum::pluck(fn(BackedEnum $case) => $case->isOdd()); // [true, false, true] BackedEnum::pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] -BackedEnum::pluck(fn (BackedEnum $case) => $case->isOdd(), fn (BackedEnum $case) => $case->name); // ['one' => true, +BackedEnum::pluck(fn(BackedEnum $case) => $case->isOdd(), fn(BackedEnum $case) => $case->name); // ['one' => true, BackedEnum::filter('isOdd'); // CasesCollection -BackedEnum::filter(fn (BackedEnum $case) => $case->isOdd()); // CasesCollection +BackedEnum::filter(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection BackedEnum::only('two', 'three'); // CasesCollection BackedEnum::except('two', 'three'); // CasesCollection BackedEnum::onlyValues(2, 3); // CasesCollection<> @@ -273,9 +273,9 @@ BackedEnum::exceptValues(2, 3); // CasesCollection<>'two' => false, 'three' => t BackedEnum::sort(); // CasesCollection BackedEnum::sortDesc(); // CasesCollection BackedEnum::sortByValue(); // CasesCollection -BackedEnum::sortDescByValue(); // CasesCollection +BackedEnum::sortByDescValue(); // CasesCollection BackedEnum::sortBy('color'); // CasesCollection -BackedEnum::sortDescBy(fn (BackedEnum $case) => $case->color()); // CasesCollection +BackedEnum::sortByDesc(fn(BackedEnum $case) => $case->color()); // CasesCollection ``` @@ -312,7 +312,7 @@ PureEnum::collect()->cases(); // [PureEnum::one, PureEnum::two, PureEnum::three] Sometimes we may need to extract only the first case of the collection: ```php -PureEnum::filter(fn (PureEnum $case) => !$case->isOdd())->first(); // PureEnum::two +PureEnum::filter(fn(PureEnum $case) => !$case->isOdd())->first(); // PureEnum::two ``` For reference, here are all the operations available in `CasesCollection`: @@ -329,11 +329,11 @@ PureEnum::collect()->names(); // ['one', 'two', 'three'] PureEnum::collect()->values(); // [] PureEnum::collect()->pluck(); // ['one', 'two', 'three'] PureEnum::collect()->pluck('color'); // ['red', 'green', 'blue'] -PureEnum::collect()->pluck(fn (PureEnum $case) => $case->isOdd()); // [true, false, true] +PureEnum::collect()->pluck(fn(PureEnum $case) => $case->isOdd()); // [true, false, true] PureEnum::collect()->pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] -PureEnum::collect()->pluck(fn (PureEnum $case) => $case->isOdd(), fn (PureEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] +PureEnum::collect()->pluck(fn(PureEnum $case) => $case->isOdd(), fn(PureEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] PureEnum::collect()->filter('isOdd'); // CasesCollection -PureEnum::collect()->filter(fn (PureEnum $case) => $case->isOdd()); // CasesCollection +PureEnum::collect()->filter(fn(PureEnum $case) => $case->isOdd()); // CasesCollection PureEnum::collect()->only('two', 'three'); // CasesCollection PureEnum::collect()->except('two', 'three'); // CasesCollection PureEnum::collect()->onlyValues(2, 3); // CasesCollection<> @@ -341,9 +341,9 @@ PureEnum::collect()->exceptValues(2, 3); // CasesCollection<> PureEnum::collect()->sort(); // CasesCollection PureEnum::collect()->sortDesc(); // CasesCollection PureEnum::collect()->sortByValue(); // CasesCollection<> -PureEnum::collect()->sortDescByValue(); // CasesCollection<> +PureEnum::collect()->sortByDescValue(); // CasesCollection<> PureEnum::collect()->sortBy('color'); // CasesCollection -PureEnum::collect()->sortDescBy(fn (PureEnum $case) => $case->color()); // CasesCollection +PureEnum::collect()->sortByDesc(fn(PureEnum $case) => $case->color()); // CasesCollection BackedEnum::collect()->cases(); // [BackedEnum::one, BackedEnum::two, BackedEnum::three] BackedEnum::collect()->count(); // 3 @@ -356,11 +356,11 @@ BackedEnum::collect()->names(); // ['one', 'two', 'three'] BackedEnum::collect()->values(); // [1, 2, 3] BackedEnum::collect()->pluck(); // [1, 2, 3] BackedEnum::collect()->pluck('color'); // ['red', 'green', 'blue'] -BackedEnum::collect()->pluck(fn (BackedEnum $case) => $case->isOdd()); // [true, false, true] +BackedEnum::collect()->pluck(fn(BackedEnum $case) => $case->isOdd()); // [true, false, true] BackedEnum::collect()->pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] -BackedEnum::collect()->pluck(fn (BackedEnum $case) => $case->isOdd(), fn (BackedEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] +BackedEnum::collect()->pluck(fn(BackedEnum $case) => $case->isOdd(), fn(BackedEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] BackedEnum::collect()->filter('isOdd'); // CasesCollection -BackedEnum::collect()->filter(fn (BackedEnum $case) => $case->isOdd()); // CasesCollection +BackedEnum::collect()->filter(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection BackedEnum::collect()->only('two', 'three'); // CasesCollection BackedEnum::collect()->except('two', 'three'); // CasesCollection BackedEnum::collect()->onlyValues(2, 3); // CasesCollection @@ -368,9 +368,9 @@ BackedEnum::collect()->exceptValues(2, 3); // CasesCollection BackedEnum::collect()->sort(); // CasesCollection BackedEnum::collect()->sortDesc(); // CasesCollection BackedEnum::collect()->sortByValue(); // CasesCollection -BackedEnum::collect()->sortDescByValue(); // CasesCollection +BackedEnum::collect()->sortByDescValue(); // CasesCollection BackedEnum::collect()->sortBy('color'); // CasesCollection -BackedEnum::collect()->sortDescBy(fn (BackedEnum $case) => $case->color()); // CasesCollection +BackedEnum::collect()->sortByDesc(fn(BackedEnum $case) => $case->color()); // CasesCollection ``` ## ๐Ÿ“† Change log From 4fb572b74d98de35d2be4de2285864af6d5c09ab Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 16 Sep 2024 22:56:44 -0300 Subject: [PATCH 08/49] Reorder methods --- src/CasesCollection.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index b89629b..e083e20 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -34,23 +34,23 @@ final public function __construct(protected array $cases) } /** - * Retrieve the iterable cases. + * Retrieve the cases. * - * @return Traversable + * @return array */ - public function getIterator(): Traversable + public function cases(): array { - yield from $this->cases; + return $this->cases; } /** - * Retrieve the cases. + * Retrieve the iterable cases. * - * @return array + * @return Traversable */ - public function cases(): array + public function getIterator(): Traversable { - return $this->cases; + yield from $this->cases; } /** From 15507990ae96f4f5fb41ab832263aeb02c8abc24 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 16 Sep 2024 23:01:25 -0300 Subject: [PATCH 09/49] Make the first argument of pluck mandatory --- src/CasesCollection.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index e083e20..f0926b2 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -162,14 +162,13 @@ public function values(): array * * @template TPluckValue * - * @param (callable(TValue): array-key)|string|null $value + * @param (callable(TValue): array-key)|string $value * @param (callable(TValue): TPluckValue)|string|null $key * @return array */ - public function pluck(callable|string $value = null, callable|string $key = null): array + public function pluck(callable|string $value, callable|string $key = null): array { $result = []; - $value ??= $this->enumIsBacked ? 'value' : 'name'; foreach ($this->cases as $case) { if ($key === null) { From 45db2bf9f6a569548fffbefa73a0fd2e67bbe37c Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 17 Sep 2024 22:21:49 -0300 Subject: [PATCH 10/49] Rename cases to all --- src/CasesCollection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index f0926b2..b17cc78 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -34,11 +34,11 @@ final public function __construct(protected array $cases) } /** - * Retrieve the cases. + * Retrieve all the cases. * * @return array */ - public function cases(): array + public function all(): array { return $this->cases; } From 93c314dfdac257912c98f72eb2f596282302e4d3 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 13:00:49 -0300 Subject: [PATCH 11/49] Add new methods --- src/CasesCollection.php | 130 ++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index b17cc78..c9596dd 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -6,13 +6,12 @@ use Countable; use IteratorAggregate; use Traversable; -use UnitEnum; /** * The collection of enum cases. * * @template TKey of array-key - * @template-covariant TValue of UnitEnum|BackedEnum + * @template TValue * * @implements IteratorAggregate */ @@ -30,17 +29,15 @@ class CasesCollection implements Countable, IteratorAggregate */ final public function __construct(protected array $cases) { - $this->enumIsBacked = $this->first() instanceof BackedEnum; + $this->enumIsBacked = reset($cases) instanceof BackedEnum; } /** - * Retrieve all the cases. - * - * @return array + * Retrieve the count of cases. */ - public function all(): array + public function count(): int { - return $this->cases; + return count($this->cases); } /** @@ -54,11 +51,39 @@ public function getIterator(): Traversable } /** - * Retrieve the count of cases. + * Retrieve all the cases as a plain array. + * + * @return array */ - public function count(): int + public function all(): array { - return count($this->cases); + return $this->cases; + } + + /** + * Retrieve all the cases as a plain array recursively. + * + * @return array + */ + public function toArray(): array + { + $array = []; + + foreach ($this->cases as $key => $value) { + $array[$key] = $value instanceof self ? $value->toArray() : $value; + } + + return $array; + } + + /** + * Add cases to the collection. + * + * @param TValue ...$cases + */ + public function add(mixed ...$cases): static + { + return new static([...$this->cases, ...$cases]); } /** @@ -84,11 +109,22 @@ public function first(callable $callback = null, mixed $default = null): mixed } /** - * Retrieve the cases keyed by their own name. + * Retrieve the mapped cases. * - * @return array + * @param callable(TValue): mixed $callback */ - public function keyByName(): array + public function map(callable $callback): static + { + $keys = array_keys($this->cases); + $values = array_map($callback, $this->cases, $keys); + + return new static(array_combine($keys, $values)); + } + + /** + * Retrieve the cases keyed by their own name. + */ + public function keyByName(): static { return $this->keyBy('name'); } @@ -97,44 +133,42 @@ public function keyByName(): array * Retrieve the cases keyed by the given key. * * @param (callable(TValue): array-key)|string $key - * @return array */ - public function keyBy(callable|string $key): array + public function keyBy(callable|string $key): static { - $result = []; + $keyed = []; foreach ($this->cases as $case) { - $result[$case->get($key)] = $case; // @phpstan-ignore method.notFound + $keyed[$case->get($key)] = $case; } - return $result; + return new static($keyed); } /** * Retrieve the cases keyed by their own value. - * - * @return array */ - public function keyByValue(): array + public function keyByValue(): static { - return $this->enumIsBacked ? $this->keyBy('value') : []; + return $this->enumIsBacked ? $this->keyBy('value') : new static([]); } /** * Retrieve the cases grouped by the given key. * * @param (callable(TValue): array-key)|string $key - * @return array */ - public function groupBy(callable|string $key): array + public function groupBy(callable|string $key): static { - $result = []; + $grouped = []; foreach ($this->cases as $case) { - $result[$case->get($key)][] = $case; // @phpstan-ignore method.notFound + $grouped[$case->get($key)] ??= new static([]); + + $grouped[$case->get($key)]->add($case); } - return $result; + return new static($grouped); } /** @@ -172,9 +206,9 @@ public function pluck(callable|string $value, callable|string $key = null): arra foreach ($this->cases as $case) { if ($key === null) { - $result[] = $case->get($value); // @phpstan-ignore method.notFound + $result[] = $case->get($value); } else { - $result[$case->get($key)] = $case->get($value); // @phpstan-ignore-line + $result[$case->get($key)] = $case->get($value); } } @@ -188,7 +222,7 @@ public function pluck(callable|string $value, callable|string $key = null): arra */ public function filter(callable|string $filter): static { - // @phpstan-ignore-next-line + /** @phpstan-ignore method.nonObject */ $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->get($filter) === true; return new static(array_filter($this->cases, $callback)); @@ -199,7 +233,7 @@ public function filter(callable|string $filter): static */ public function only(string ...$name): static { - return $this->filter(fn(UnitEnum $case) => in_array($case->name, $name)); + return $this->filter(fn(mixed $case) => in_array($case->name, $name)); } /** @@ -207,7 +241,7 @@ public function only(string ...$name): static */ public function except(string ...$name): static { - return $this->filter(fn(UnitEnum $case) => !in_array($case->name, $name)); + return $this->filter(fn(mixed $case) => !in_array($case->name, $name)); } /** @@ -215,10 +249,7 @@ public function except(string ...$name): static */ public function onlyValues(string|int ...$value): static { - return $this->filter(function (UnitEnum $case) use ($value) { - /** @var BackedEnum $case */ - return $this->enumIsBacked && in_array($case->value, $value, true); - }); + return $this->filter(fn(mixed $case) => $this->enumIsBacked && in_array($case->value, $value, true)); } /** @@ -226,10 +257,7 @@ public function onlyValues(string|int ...$value): static */ public function exceptValues(string|int ...$value): static { - return $this->filter(function (UnitEnum $case) use ($value) { - /** @var BackedEnum $case */ - return $this->enumIsBacked && !in_array($case->value, $value, true); - }); + return $this->filter(fn(mixed $case) => $this->enumIsBacked && !in_array($case->value, $value, true)); } /** @@ -249,11 +277,19 @@ public function sortBy(callable|string $key): static { $cases = $this->cases; - uasort($cases, fn(mixed $a, mixed $b) => $a->get($key) <=> $b->get($key)); // @phpstan-ignore-line + uasort($cases, fn(mixed $a, mixed $b) => $a->get($key) <=> $b->get($key)); return new static($cases); } + /** + * Retrieve a new collection of cases sorted by their own value ascending. + */ + public function sortByValue(): static + { + return $this->enumIsBacked ? $this->sortBy('value') : new static([]); + } + /** * Retrieve a new collection of cases sorted by their own name descending. */ @@ -271,19 +307,11 @@ public function sortByDesc(callable|string $key): static { $cases = $this->cases; - uasort($cases, fn(mixed $a, mixed $b) => $a->get($key) > $b->get($key) ? -1 : 1); // @phpstan-ignore-line + uasort($cases, fn(mixed $a, mixed $b) => $b->get($key) <=> $a->get($key)); return new static($cases); } - /** - * Retrieve a new collection of cases sorted by their own value ascending. - */ - public function sortByValue(): static - { - return $this->enumIsBacked ? $this->sortBy('value') : new static([]); - } - /** * Retrieve a new collection of cases sorted by their own value descending. */ From f4949453d0ab4515f3dbb0793e5e7cc9b2dcba02 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 13:46:11 -0300 Subject: [PATCH 12/49] Fix docblock --- src/CasesCollection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index c9596dd..9c5f6d5 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -196,8 +196,8 @@ public function values(): array * * @template TPluckValue * - * @param (callable(TValue): array-key)|string $value - * @param (callable(TValue): TPluckValue)|string|null $key + * @param (callable(TValue): TPluckValue)|string $value + * @param (callable(TValue): array-key)|string|null $key * @return array */ public function pluck(callable|string $value, callable|string $key = null): array From 6bdd9f33d08c96a74f68d0835a88cd80530a7e17 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 13:56:32 -0300 Subject: [PATCH 13/49] Remove default parameter from the first() method --- src/CasesCollection.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 9c5f6d5..2430940 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -89,13 +89,10 @@ public function add(mixed ...$cases): static /** * Retrieve the first case. * - * @template TFirstDefault - * * @param (callable(TValue, TKey): bool)|null $callback - * @param TFirstDefault $default - * @return TValue|TFirstDefault + * @return ?TValue */ - public function first(callable $callback = null, mixed $default = null): mixed + public function first(callable $callback = null): mixed { $callback ??= fn() => true; @@ -105,7 +102,7 @@ public function first(callable $callback = null, mixed $default = null): mixed } } - return $default; + return null; } /** From 74edfb0713cda1243b4a35782c1352a4e8f16b36 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 14:16:13 -0300 Subject: [PATCH 14/49] Let map() return an array --- src/CasesCollection.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 2430940..08b40a9 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -106,16 +106,19 @@ public function first(callable $callback = null): mixed } /** - * Retrieve the mapped cases. + * Retrieve the result of mapping over the cases. * - * @param callable(TValue): mixed $callback + * @template TMapValue + * + * @param callable(TValue, TKey): TMapValue $callback + * @return array */ - public function map(callable $callback): static + public function map(callable $callback): array { $keys = array_keys($this->cases); $values = array_map($callback, $this->cases, $keys); - return new static(array_combine($keys, $values)); + return array_combine($keys, $values); } /** From 7e1989a2b08460c2ca73a70252cf328439b4677c Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 14:17:03 -0300 Subject: [PATCH 15/49] Added new methods and generics --- src/Concerns/CollectsCases.php | 194 ++++++++++++++++++--------------- 1 file changed, 106 insertions(+), 88 deletions(-) diff --git a/src/Concerns/CollectsCases.php b/src/Concerns/CollectsCases.php index f5d30b3..107dc1f 100644 --- a/src/Concerns/CollectsCases.php +++ b/src/Concerns/CollectsCases.php @@ -5,219 +5,237 @@ use Cerbero\Enum\CasesCollection; /** - * The trait to collect cases of an enum. - * + * The trait to collect the cases of an enum. */ trait CollectsCases { /** - * Retrieve a collection with all the cases + * Retrieve a collection with all the cases. * - * @return CasesCollection + * @return CasesCollection */ public static function collect(): CasesCollection { - return new CasesCollection(static::cases()); + return new CasesCollection(self::cases()); } /** - * Retrieve the count of cases - * - * @return int + * Retrieve the count of cases. */ public static function count(): int { - return static::collect()->count(); + return self::collect()->count(); } /** - * Retrieve all cases keyed by name + * Retrieve the first case. * - * @return array + * @param (callable(self, array-key): bool)|null $callback */ - public static function casesByName(): array + public function first(callable $callback = null): ?self { - return static::collect()->keyByName(); + return self::collect()->first($callback); } /** - * Retrieve all cases keyed by value + * Retrieve the result of mapping over all the cases. + * + * @template TMapValue * - * @return array + * @param callable(self, array-key): TMapValue $callback + * @return array */ - public static function casesByValue(): array + public function map(callable $callback): array { - return static::collect()->keyByValue(); + return self::collect()->map($callback); } /** - * Retrieve all cases keyed by the given key + * Retrieve all the cases keyed by their own name. * - * @param callable|string $key - * @return array + * @return CasesCollection */ - public static function casesBy(callable|string $key): array + public static function keyByName(): CasesCollection { - return static::collect()->keyBy($key); + return self::collect()->keyByName(); } /** - * Retrieve all cases grouped by the given key + * Retrieve all the cases keyed by the given key. * - * @param callable|string $key - * @return array + * @param (callable(self): array-key)|string $key + * @return CasesCollection */ - public static function groupBy(callable|string $key): array + public static function keyBy(callable|string $key): CasesCollection { - return static::collect()->groupBy($key); + return self::collect()->keyBy($key); } /** - * Retrieve all the names of the cases + * Retrieve all the cases keyed by their own value. * - * @return array + * @return CasesCollection + */ + public static function keyByValue(): CasesCollection + { + return self::collect()->keyByValue(); + } + + /** + * Retrieve all the cases grouped by the given key. + * + * @param (callable(self): array-key)|string $key + * @return CasesCollection> + */ + public static function groupBy(callable|string $key): CasesCollection + { + return self::collect()->groupBy($key); + } + + /** + * Retrieve the name of all the cases. + * + * @return string[] */ public static function names(): array { - return static::collect()->names(); + return self::collect()->names(); } /** - * Retrieve all the values of the backed cases + * Retrieve the value of all the backed cases. * - * @return array + * @return list */ public static function values(): array { - return static::collect()->values(); + return self::collect()->values(); } /** - * Retrieve a collection with the filtered cases + * Retrieve an array of values optionally keyed by the given key. + * + * @template TPluckValue * - * @param callable|string $filter - * @return CasesCollection + * @param (callable(self): TPluckValue)|string $value + * @param (callable(self): array-key)|string|null $key + * @return array */ - public static function filter(callable|string $filter): CasesCollection + public static function pluck(callable|string $value, callable|string $key = null): array { - return static::collect()->filter($filter); + return self::collect()->pluck($value, $key); } /** - * Retrieve a collection of cases having the given names + * Retrieve only the filtered cases. * - * @param string ...$name - * @return CasesCollection + * @param (callable(self): bool)|string $filter + * @return CasesCollection */ - public static function only(string ...$name): CasesCollection + public static function filter(callable|string $filter): CasesCollection { - return static::collect()->only(...$name); + return self::collect()->filter($filter); } /** - * Retrieve a collection of cases not having the given names + * Retrieve only the cases having the given names. * - * @param string ...$name - * @return CasesCollection + * @return CasesCollection */ - public static function except(string ...$name): CasesCollection + public static function only(string ...$names): CasesCollection { - return static::collect()->except(...$name); + return self::collect()->only(...$names); } /** - * Retrieve a collection of backed cases having the given values + * Retrieve only the cases not having the given names. * - * @param string|int ...$value - * @return CasesCollection + * @return CasesCollection */ - public static function onlyValues(string|int ...$value): CasesCollection + public static function except(string ...$names): CasesCollection { - return static::collect()->onlyValues(...$value); + return self::collect()->except(...$names); } /** - * Retrieve a collection of backed cases not having the given values + * Retrieve only the cases having the given values. * - * @param string|int ...$value - * @return CasesCollection + * @return CasesCollection */ - public static function exceptValues(string|int ...$value): CasesCollection + public static function onlyValues(string|int ...$values): CasesCollection { - return static::collect()->exceptValues(...$value); + return self::collect()->onlyValues(...$values); } /** - * Retrieve an array of values optionally keyed by the given key + * Retrieve only the cases not having the given values. * - * @param callable|string|null $value - * @param callable|string|null $key - * @return array + * @return CasesCollection */ - public static function pluck(callable|string $value = null, callable|string $key = null): array + public static function exceptValues(string|int ...$values): CasesCollection { - return static::collect()->pluck($value, $key); + return self::collect()->exceptValues(...$values); } /** - * Retrieve a collection of cases sorted by name ascending + * Retrieve all the cases sorted by their own name ascending. * - * @return CasesCollection + * @return CasesCollection */ public static function sort(): CasesCollection { - return static::collect()->sort(); + return self::collect()->sort(); } /** - * Retrieve a collection of cases sorted by name descending + * Retrieve all the cases sorted by the given key ascending. * - * @return CasesCollection + * @param (callable(self): mixed)|string $key + * @return CasesCollection */ - public static function sortDesc(): CasesCollection + public static function sortBy(callable|string $key): CasesCollection { - return static::collect()->sortDesc(); + return self::collect()->sortBy($key); } /** - * Retrieve a collection of cases sorted by value ascending + * Retrieve all the cases sorted by their own value ascending. * - * @return CasesCollection + * @return CasesCollection */ public static function sortByValue(): CasesCollection { - return static::collect()->sortByValue(); + return self::collect()->sortByValue(); } /** - * Retrieve a collection of cases sorted by value descending + * Retrieve all the cases sorted by their own name descending. * - * @return CasesCollection + * @return CasesCollection */ - public static function sortByDescValue(): CasesCollection + public static function sortDesc(): CasesCollection { - return static::collect()->sortByDescValue(); + return self::collect()->sortDesc(); } /** - * Retrieve a collection of cases sorted by the given key ascending + * Retrieve all the cases sorted by the given key descending. * - * @param callable|string $key - * @return CasesCollection + * @param (callable(self): mixed)|string $key + * @return CasesCollection */ - public static function sortBy(callable|string $key): CasesCollection + public static function sortByDesc(callable|string $key): CasesCollection { - return static::collect()->sortBy($key); + return self::collect()->sortByDesc($key); } /** - * Retrieve a collection of cases sorted by the given key descending + * Retrieve all the cases sorted by their own value descending. * - * @param callable|string $key - * @return CasesCollection + * @return CasesCollection */ - public static function sortByDesc(callable|string $key): CasesCollection + public static function sortByDescValue(): CasesCollection { - return static::collect()->sortByDesc($key); + return self::collect()->sortByDescValue(); } } From 7539a5e364ec572e8df709585e1f569b9b5ce7af Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 14:20:25 -0300 Subject: [PATCH 16/49] Reorder methods --- src/CasesCollection.php | 88 +++++++++++++++++----------------- src/Concerns/CollectsCases.php | 68 +++++++++++++------------- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 08b40a9..9f56155 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -105,6 +105,50 @@ public function first(callable $callback = null): mixed return null; } + /** + * Retrieve all the names of the cases. + * + * @return string[] + */ + public function names(): array + { + return array_column($this->cases, 'name'); + } + + /** + * Retrieve all the values of the backed cases. + * + * @return list + */ + public function values(): array + { + return array_column($this->cases, 'value'); + } + + /** + * Retrieve an array of values optionally keyed by the given key. + * + * @template TPluckValue + * + * @param (callable(TValue): TPluckValue)|string $value + * @param (callable(TValue): array-key)|string|null $key + * @return array + */ + public function pluck(callable|string $value, callable|string $key = null): array + { + $result = []; + + foreach ($this->cases as $case) { + if ($key === null) { + $result[] = $case->get($value); + } else { + $result[$case->get($key)] = $case->get($value); + } + } + + return $result; + } + /** * Retrieve the result of mapping over the cases. * @@ -171,50 +215,6 @@ public function groupBy(callable|string $key): static return new static($grouped); } - /** - * Retrieve all the names of the cases. - * - * @return string[] - */ - public function names(): array - { - return array_column($this->cases, 'name'); - } - - /** - * Retrieve all the values of the backed cases. - * - * @return list - */ - public function values(): array - { - return array_column($this->cases, 'value'); - } - - /** - * Retrieve an array of values optionally keyed by the given key. - * - * @template TPluckValue - * - * @param (callable(TValue): TPluckValue)|string $value - * @param (callable(TValue): array-key)|string|null $key - * @return array - */ - public function pluck(callable|string $value, callable|string $key = null): array - { - $result = []; - - foreach ($this->cases as $case) { - if ($key === null) { - $result[] = $case->get($value); - } else { - $result[$case->get($key)] = $case->get($value); - } - } - - return $result; - } - /** * Retrieve a new collection with the filtered cases. * diff --git a/src/Concerns/CollectsCases.php b/src/Concerns/CollectsCases.php index 107dc1f..36ce1fe 100644 --- a/src/Concerns/CollectsCases.php +++ b/src/Concerns/CollectsCases.php @@ -37,6 +37,40 @@ public function first(callable $callback = null): ?self return self::collect()->first($callback); } + /** + * Retrieve the name of all the cases. + * + * @return string[] + */ + public static function names(): array + { + return self::collect()->names(); + } + + /** + * Retrieve the value of all the backed cases. + * + * @return list + */ + public static function values(): array + { + return self::collect()->values(); + } + + /** + * Retrieve an array of values optionally keyed by the given key. + * + * @template TPluckValue + * + * @param (callable(self): TPluckValue)|string $value + * @param (callable(self): array-key)|string|null $key + * @return array + */ + public static function pluck(callable|string $value, callable|string $key = null): array + { + return self::collect()->pluck($value, $key); + } + /** * Retrieve the result of mapping over all the cases. * @@ -92,40 +126,6 @@ public static function groupBy(callable|string $key): CasesCollection return self::collect()->groupBy($key); } - /** - * Retrieve the name of all the cases. - * - * @return string[] - */ - public static function names(): array - { - return self::collect()->names(); - } - - /** - * Retrieve the value of all the backed cases. - * - * @return list - */ - public static function values(): array - { - return self::collect()->values(); - } - - /** - * Retrieve an array of values optionally keyed by the given key. - * - * @template TPluckValue - * - * @param (callable(self): TPluckValue)|string $value - * @param (callable(self): array-key)|string|null $key - * @return array - */ - public static function pluck(callable|string $value, callable|string $key = null): array - { - return self::collect()->pluck($value, $key); - } - /** * Retrieve only the filtered cases. * From a385b6a187e49fd068e4dcf57c369c5fdb88132c Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 14:28:40 -0300 Subject: [PATCH 17/49] Update build workflow --- .github/workflows/build.yml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b08aacb..316c572 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ name: build on: - push: - pull_request: + push: + pull_request: jobs: tests: @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.1] + php: [8.1, 8.2, 8.3] dependency-version: [prefer-stable] os: [ubuntu-latest] @@ -25,7 +25,6 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip tools: composer:v2 coverage: none @@ -34,7 +33,7 @@ jobs: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Execute tests - run: vendor/bin/pest --verbose + run: vendor/bin/pest coverage: runs-on: ubuntu-latest @@ -63,12 +62,12 @@ jobs: - name: Upload coverage run: | - vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover + vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover - style: + linting: runs-on: ubuntu-latest - name: Coding style + name: Linting steps: - name: Checkout code @@ -77,9 +76,13 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.0 - tools: phpcs + php-version: 8.2 + tools: composer:v2 coverage: none - - name: Execute check - run: phpcs --standard=psr12 src/ + - name: Install dependencies + run: | + composer update --prefer-stable --prefer-dist --no-interaction + + - name: Execute Duster + run: vendor/bin/duster lint -u tlint,phpcodesniffer,pint,phpstan -vvv From 76555f54facc56aebc10282689ebeb52fb09b3df Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 17:10:42 -0300 Subject: [PATCH 18/49] Improve docblocks --- src/Concerns/Compares.php | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/Concerns/Compares.php b/src/Concerns/Compares.php index 73fb861..b95a538 100644 --- a/src/Concerns/Compares.php +++ b/src/Concerns/Compares.php @@ -3,16 +3,12 @@ namespace Cerbero\Enum\Concerns; /** - * The trait to compare cases of an enum. - * + * The trait to compare the cases of an enum. */ trait Compares { /** - * Determine whether the enum has the given target - * - * @param mixed $target - * @return bool + * Determine whether the enum includes the given target. */ public static function has(mixed $target): bool { @@ -26,10 +22,7 @@ public static function has(mixed $target): bool } /** - * Determine whether the enum does not have the given target - * - * @param mixed $target - * @return bool + * Determine whether the enum does not include the given target. */ public static function doesntHave(mixed $target): bool { @@ -43,10 +36,7 @@ public static function doesntHave(mixed $target): bool } /** - * Determine whether the current case matches the given target - * - * @param mixed $target - * @return bool + * Determine whether this case matches the given target. */ public function is(mixed $target): bool { @@ -54,10 +44,7 @@ public function is(mixed $target): bool } /** - * Determine whether the current case does not match the given target - * - * @param mixed $target - * @return bool + * Determine whether this case does not match the given target. */ public function isNot(mixed $target): bool { @@ -65,10 +52,9 @@ public function isNot(mixed $target): bool } /** - * Determine whether the current case matches at least one of the given targets + * Determine whether this case matches at least one of the given targets. * - * @param iterable $targets - * @return bool + * @param iterable $targets */ public function in(iterable $targets): bool { @@ -82,10 +68,9 @@ public function in(iterable $targets): bool } /** - * Determine whether the current case does not match any of the given targets + * Determine whether this case does not match any of the given targets. * - * @param iterable $targets - * @return bool + * @param iterable $targets */ public function notIn(iterable $targets): bool { From 50f37cccde34749751bb4eebbcf446019568c9e0 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 17:38:16 -0300 Subject: [PATCH 19/49] Merge the traits --- src/Concerns/Enumerates.php | 4 +--- src/Concerns/KeysAware.php | 30 ------------------------------ src/Concerns/SelfAware.php | 31 ++++++++++++++++++++++++------- 3 files changed, 25 insertions(+), 40 deletions(-) delete mode 100644 src/Concerns/KeysAware.php diff --git a/src/Concerns/Enumerates.php b/src/Concerns/Enumerates.php index 3ca0e57..abddd91 100644 --- a/src/Concerns/Enumerates.php +++ b/src/Concerns/Enumerates.php @@ -3,14 +3,12 @@ namespace Cerbero\Enum\Concerns; /** - * The trait to extend enum functionalities. - * + * The trait to supercharge the functionalities of an enum. */ trait Enumerates { use CollectsCases; use Compares; use Hydrates; - use KeysAware; use SelfAware; } diff --git a/src/Concerns/KeysAware.php b/src/Concerns/KeysAware.php deleted file mode 100644 index 9a22205..0000000 --- a/src/Concerns/KeysAware.php +++ /dev/null @@ -1,30 +0,0 @@ -$key ?? $this->$key()); - } catch (Throwable) { - $target = is_callable($key) ? 'The given callable' : "\"{$key}\""; - throw new ValueError(sprintf('%s is not a valid key for enum "%s"', $target, static::class)); - } - } -} diff --git a/src/Concerns/SelfAware.php b/src/Concerns/SelfAware.php index 6c69832..db8be98 100644 --- a/src/Concerns/SelfAware.php +++ b/src/Concerns/SelfAware.php @@ -3,17 +3,16 @@ namespace Cerbero\Enum\Concerns; use BackedEnum; +use Throwable; +use ValueError; /** * The trait to make an enum self-aware. - * */ trait SelfAware { /** - * Determine whether the enum is pure - * - * @return bool + * Determine whether the enum is pure. */ public static function isPure(): bool { @@ -21,12 +20,30 @@ public static function isPure(): bool } /** - * Determine whether the enum is backed - * - * @return bool + * Determine whether the enum is backed. */ public static function isBacked(): bool { return is_subclass_of(static::class, BackedEnum::class); } + + /** + * Retrieve the given key of this case. + * + * @template TGetValue + * + * @param (callable(self): TGetValue)|string $key + * @return TGetValue + * @throws ValueError + */ + public function get(callable|string $key): mixed + { + try { + return is_callable($key) ? $key($this) : ($this->$key ?? $this->$key()); + } catch (Throwable) { + $target = is_callable($key) ? 'The given callable' : "\"{$key}\""; + + throw new ValueError(sprintf('%s is not a valid key for the enum "%s"', $target, static::class)); + } + } } From 2ed6243c2ebf403c0a5c3ef35a0ebbee2b8e0c86 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 18:02:39 -0300 Subject: [PATCH 20/49] Improve docblocks --- src/Concerns/Hydrates.php | 55 ++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/src/Concerns/Hydrates.php b/src/Concerns/Hydrates.php index 7abf09c..e42e2f0 100644 --- a/src/Concerns/Hydrates.php +++ b/src/Concerns/Hydrates.php @@ -7,15 +7,13 @@ /** * The trait to hydrate an enum. - * */ trait Hydrates { /** - * Retrieve the case hydrated from the given name (called by pure enums only) + * Retrieve the case hydrated from the given name or fail. + * This method can be called by pure enums only. * - * @param string $name - * @return static * @throws ValueError */ public static function from(string $name): static @@ -24,21 +22,8 @@ public static function from(string $name): static } /** - * Retrieve the case hydrated from the given name or NULL (called by pure enums only) + * Retrieve the case hydrated from the given name or fail. * - * @param string $name - * @return static|null - */ - public static function tryFrom(string $name): ?static - { - return static::tryFromName($name); - } - - /** - * Retrieve the case hydrated from the given name - * - * @param string $name - * @return static * @throws ValueError */ public static function fromName(string $name): static @@ -51,10 +36,7 @@ public static function fromName(string $name): static } /** - * Retrieve the case hydrated from the given name or NULL - * - * @param string $name - * @return static|null + * Retrieve the case hydrated from the given name or NULL. */ public static function tryFromName(string $name): ?static { @@ -68,17 +50,25 @@ public static function tryFromName(string $name): ?static } /** - * Retrieve cases hydrated from the given key + * Retrieve the case hydrated from the given name or NULL. + * This method can be called by pure enums only. + */ + public static function tryFrom(string $name): ?static + { + return static::tryFromName($name); + } + + /** + * Retrieve all the cases hydrated from the given key or fail. * - * @param callable|string $key - * @param mixed $value - * @return CasesCollection + * @param (callable(self): mixed)|string $key + * @return CasesCollection * @throws ValueError */ public static function fromKey(callable|string $key, mixed $value): CasesCollection { - if ($result = static::tryFromKey($key, $value)) { - return $result; + if ($cases = static::tryFromKey($key, $value)) { + return $cases; } $target = is_callable($key) ? 'given callable key' : "key \"{$key}\""; @@ -87,13 +77,12 @@ public static function fromKey(callable|string $key, mixed $value): CasesCollect } /** - * Retrieve cases hydrated from the given key or NULL + * Retrieve all the cases hydrated from the given key or NULL. * - * @param callable|string $key - * @param mixed $value - * @return CasesCollection|null + * @param (callable(self): mixed)|string $key + * @return ?CasesCollection */ - public static function tryFromKey(callable|string $key, mixed $value): CasesCollection|null + public static function tryFromKey(callable|string $key, mixed $value): ?CasesCollection { $cases = []; From 89d89e4078f2103644f569696208ee770afe173d Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 18:14:41 -0300 Subject: [PATCH 21/49] Keep message consistent --- src/Concerns/SelfAware.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Concerns/SelfAware.php b/src/Concerns/SelfAware.php index db8be98..7ab7a97 100644 --- a/src/Concerns/SelfAware.php +++ b/src/Concerns/SelfAware.php @@ -43,7 +43,7 @@ public function get(callable|string $key): mixed } catch (Throwable) { $target = is_callable($key) ? 'The given callable' : "\"{$key}\""; - throw new ValueError(sprintf('%s is not a valid key for the enum "%s"', $target, static::class)); + throw new ValueError(sprintf('%s is not a valid key for enum "%s"', $target, static::class)); } } } From 6c6a8631f0f248035fafca471c9c1ceffa4aa71e Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 19:14:03 -0300 Subject: [PATCH 22/49] Make methods static --- src/Concerns/CollectsCases.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Concerns/CollectsCases.php b/src/Concerns/CollectsCases.php index 36ce1fe..2be87dd 100644 --- a/src/Concerns/CollectsCases.php +++ b/src/Concerns/CollectsCases.php @@ -32,7 +32,7 @@ public static function count(): int * * @param (callable(self, array-key): bool)|null $callback */ - public function first(callable $callback = null): ?self + public static function first(callable $callback = null): ?self { return self::collect()->first($callback); } @@ -79,7 +79,7 @@ public static function pluck(callable|string $value, callable|string $key = null * @param callable(self, array-key): TMapValue $callback * @return array */ - public function map(callable $callback): array + public static function map(callable $callback): array { return self::collect()->map($callback); } From 34e395e3bf6f942ce773779ba75d658ce3954f8a Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 19:15:17 -0300 Subject: [PATCH 23/49] Replace static with self --- src/Concerns/Compares.php | 6 +++--- src/Concerns/Hydrates.php | 16 ++++++++-------- tests/BackedEnum.php | 18 +++++++++--------- tests/PureEnum.php | 18 +++++++++--------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Concerns/Compares.php b/src/Concerns/Compares.php index b95a538..4e4ba4c 100644 --- a/src/Concerns/Compares.php +++ b/src/Concerns/Compares.php @@ -12,7 +12,7 @@ trait Compares */ public static function has(mixed $target): bool { - foreach (static::cases() as $case) { + foreach (self::cases() as $case) { if ($case->is($target)) { return true; } @@ -26,7 +26,7 @@ public static function has(mixed $target): bool */ public static function doesntHave(mixed $target): bool { - foreach (static::cases() as $case) { + foreach (self::cases() as $case) { if ($case->is($target)) { return false; } @@ -40,7 +40,7 @@ public static function doesntHave(mixed $target): bool */ public function is(mixed $target): bool { - return in_array($target, [$this, static::isPure() ? $this->name : $this->value], true); + return in_array($target, [$this, self::isPure() ? $this->name : $this->value], true); } /** diff --git a/src/Concerns/Hydrates.php b/src/Concerns/Hydrates.php index e42e2f0..273c66d 100644 --- a/src/Concerns/Hydrates.php +++ b/src/Concerns/Hydrates.php @@ -18,7 +18,7 @@ trait Hydrates */ public static function from(string $name): static { - return static::fromName($name); + return self::fromName($name); } /** @@ -28,11 +28,11 @@ public static function from(string $name): static */ public static function fromName(string $name): static { - if ($case = static::tryFromName($name)) { + if ($case = self::tryFromName($name)) { return $case; } - throw new ValueError(sprintf('"%s" is not a valid name for enum "%s"', $name, static::class)); + throw new ValueError(sprintf('"%s" is not a valid name for enum "%s"', $name, self::class)); } /** @@ -40,7 +40,7 @@ public static function fromName(string $name): static */ public static function tryFromName(string $name): ?static { - foreach (static::cases() as $case) { + foreach (self::cases() as $case) { if ($case->name === $name) { return $case; } @@ -55,7 +55,7 @@ public static function tryFromName(string $name): ?static */ public static function tryFrom(string $name): ?static { - return static::tryFromName($name); + return self::tryFromName($name); } /** @@ -67,13 +67,13 @@ public static function tryFrom(string $name): ?static */ public static function fromKey(callable|string $key, mixed $value): CasesCollection { - if ($cases = static::tryFromKey($key, $value)) { + if ($cases = self::tryFromKey($key, $value)) { return $cases; } $target = is_callable($key) ? 'given callable key' : "key \"{$key}\""; - throw new ValueError(sprintf('Invalid value for the %s for enum "%s"', $target, static::class)); + throw new ValueError(sprintf('Invalid value for the %s for enum "%s"', $target, self::class)); } /** @@ -86,7 +86,7 @@ public static function tryFromKey(callable|string $key, mixed $value): ?CasesCol { $cases = []; - foreach (static::cases() as $case) { + foreach (self::cases() as $case) { if ($case->get($key) === $value) { $cases[] = $case; } diff --git a/tests/BackedEnum.php b/tests/BackedEnum.php index 2c224b6..df216dc 100644 --- a/tests/BackedEnum.php +++ b/tests/BackedEnum.php @@ -24,9 +24,9 @@ enum BackedEnum: int public function color(): string { return match ($this) { - static::one => 'red', - static::two => 'green', - static::three => 'blue', + self::one => 'red', + self::two => 'green', + self::three => 'blue', }; } @@ -38,9 +38,9 @@ public function color(): string public function shape(): string { return match ($this) { - static::one => 'triangle', - static::two => 'square', - static::three => 'circle', + self::one => 'triangle', + self::two => 'square', + self::three => 'circle', }; } @@ -52,9 +52,9 @@ public function shape(): string public function isOdd(): bool { return match ($this) { - static::one => true, - static::two => false, - static::three => true, + self::one => true, + self::two => false, + self::three => true, }; } } diff --git a/tests/PureEnum.php b/tests/PureEnum.php index 648c7ce..92f1206 100644 --- a/tests/PureEnum.php +++ b/tests/PureEnum.php @@ -24,9 +24,9 @@ enum PureEnum public function color(): string { return match ($this) { - static::one => 'red', - static::two => 'green', - static::three => 'blue', + self::one => 'red', + self::two => 'green', + self::three => 'blue', }; } @@ -38,9 +38,9 @@ public function color(): string public function shape(): string { return match ($this) { - static::one => 'triangle', - static::two => 'square', - static::three => 'circle', + self::one => 'triangle', + self::two => 'square', + self::three => 'circle', }; } @@ -52,9 +52,9 @@ public function shape(): string public function isOdd(): bool { return match ($this) { - static::one => true, - static::two => false, - static::three => true, + self::one => true, + self::two => false, + self::three => true, }; } } From e4ed597925588556564b59829b0cd34223630320 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 19:37:47 -0300 Subject: [PATCH 24/49] Add method to get all keys --- src/Concerns/SelfAware.php | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Concerns/SelfAware.php b/src/Concerns/SelfAware.php index 7ab7a97..39f794d 100644 --- a/src/Concerns/SelfAware.php +++ b/src/Concerns/SelfAware.php @@ -3,6 +3,8 @@ namespace Cerbero\Enum\Concerns; use BackedEnum; +use ReflectionClass; +use ReflectionMethod; use Throwable; use ValueError; @@ -16,7 +18,7 @@ trait SelfAware */ public static function isPure(): bool { - return !static::isBacked(); + return !self::isBacked(); } /** @@ -24,7 +26,26 @@ public static function isPure(): bool */ public static function isBacked(): bool { - return is_subclass_of(static::class, BackedEnum::class); + return is_subclass_of(self::class, BackedEnum::class); + } + + /** + * Retrieve all the keys of the enum. + * + * @return string[] + */ + public static function keys(): array + { + $enum = new ReflectionClass(self::class); + $keys = self::isPure() ? ['name'] : ['name', 'value']; + + foreach ($enum->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if (! $method->isStatic() && $method->getFileName() == $enum->getFileName()) { + $keys[] = $method->getShortName(); + } + } + + return $keys; } /** @@ -43,7 +64,7 @@ public function get(callable|string $key): mixed } catch (Throwable) { $target = is_callable($key) ? 'The given callable' : "\"{$key}\""; - throw new ValueError(sprintf('%s is not a valid key for enum "%s"', $target, static::class)); + throw new ValueError(sprintf('%s is not a valid key for enum "%s"', $target, self::class)); } } } From 7cc424b5e8415ed826ba851d12ddcc5cb6b542b5 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 19 Sep 2024 19:40:36 -0300 Subject: [PATCH 25/49] Rename get() to resolveKey() --- src/CasesCollection.php | 16 ++++++++-------- src/Concerns/Hydrates.php | 2 +- src/Concerns/SelfAware.php | 2 +- tests/PureEnumTest.php | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 9f56155..6c62255 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -140,9 +140,9 @@ public function pluck(callable|string $value, callable|string $key = null): arra foreach ($this->cases as $case) { if ($key === null) { - $result[] = $case->get($value); + $result[] = $case->resolveKey($value); } else { - $result[$case->get($key)] = $case->get($value); + $result[$case->resolveKey($key)] = $case->resolveKey($value); } } @@ -183,7 +183,7 @@ public function keyBy(callable|string $key): static $keyed = []; foreach ($this->cases as $case) { - $keyed[$case->get($key)] = $case; + $keyed[$case->resolveKey($key)] = $case; } return new static($keyed); @@ -207,9 +207,9 @@ public function groupBy(callable|string $key): static $grouped = []; foreach ($this->cases as $case) { - $grouped[$case->get($key)] ??= new static([]); + $grouped[$case->resolveKey($key)] ??= new static([]); - $grouped[$case->get($key)]->add($case); + $grouped[$case->resolveKey($key)]->add($case); } return new static($grouped); @@ -223,7 +223,7 @@ public function groupBy(callable|string $key): static public function filter(callable|string $filter): static { /** @phpstan-ignore method.nonObject */ - $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->get($filter) === true; + $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->resolveKey($filter) === true; return new static(array_filter($this->cases, $callback)); } @@ -277,7 +277,7 @@ public function sortBy(callable|string $key): static { $cases = $this->cases; - uasort($cases, fn(mixed $a, mixed $b) => $a->get($key) <=> $b->get($key)); + uasort($cases, fn(mixed $a, mixed $b) => $a->resolveKey($key) <=> $b->resolveKey($key)); return new static($cases); } @@ -307,7 +307,7 @@ public function sortByDesc(callable|string $key): static { $cases = $this->cases; - uasort($cases, fn(mixed $a, mixed $b) => $b->get($key) <=> $a->get($key)); + uasort($cases, fn(mixed $a, mixed $b) => $b->resolveKey($key) <=> $a->resolveKey($key)); return new static($cases); } diff --git a/src/Concerns/Hydrates.php b/src/Concerns/Hydrates.php index 273c66d..630ad12 100644 --- a/src/Concerns/Hydrates.php +++ b/src/Concerns/Hydrates.php @@ -87,7 +87,7 @@ public static function tryFromKey(callable|string $key, mixed $value): ?CasesCol $cases = []; foreach (self::cases() as $case) { - if ($case->get($key) === $value) { + if ($case->resolveKey($key) === $value) { $cases[] = $case; } } diff --git a/src/Concerns/SelfAware.php b/src/Concerns/SelfAware.php index 39f794d..48fb531 100644 --- a/src/Concerns/SelfAware.php +++ b/src/Concerns/SelfAware.php @@ -57,7 +57,7 @@ public static function keys(): array * @return TGetValue * @throws ValueError */ - public function get(callable|string $key): mixed + public function resolveKey(callable|string $key): mixed { try { return is_callable($key) ? $key($this) : ($this->$key ?? $this->$key()); diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index 63005ef..83a947c 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -341,7 +341,7 @@ ->toBe([PureEnum::two]); it('retrieves the key of a case') - ->expect(fn(string $key, mixed $value) => PureEnum::one->get($key) === $value) + ->expect(fn(string $key, mixed $value) => PureEnum::one->resolveKey($key) === $value) ->toBeTrue() ->with([ ['name', 'one'], @@ -350,8 +350,8 @@ ]); it('retrieves the key of a case using a closure') - ->expect(PureEnum::one->get(fn(PureEnum $case) => $case->color())) + ->expect(PureEnum::one->resolveKey(fn(PureEnum $case) => $case->color())) ->toBe('red'); -it('throws a value error when attempting to retrieve an invalid key', fn() => PureEnum::one->get('invalid')) +it('throws a value error when attempting to retrieve an invalid key', fn() => PureEnum::one->resolveKey('invalid')) ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); From ce1b6652c031f094b3e3f1eb1fb5bab5446733b9 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 21 Sep 2024 09:40:58 -0300 Subject: [PATCH 26/49] Add the value() method --- src/Concerns/SelfAware.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Concerns/SelfAware.php b/src/Concerns/SelfAware.php index 48fb531..3573cd8 100644 --- a/src/Concerns/SelfAware.php +++ b/src/Concerns/SelfAware.php @@ -67,4 +67,12 @@ public function resolveKey(callable|string $key): mixed throw new ValueError(sprintf('%s is not a valid key for enum "%s"', $target, self::class)); } } + + /** + * Retrieve the value of a backed case or the name of a pure case. + */ + public function value(): string|int + { + return $this->value ?? $this->name; + } } From 33ad8bd9b1e06e8b5c2776e5803d6d0062b68ce0 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 21 Sep 2024 09:45:48 -0300 Subject: [PATCH 27/49] Handle magic methods --- src/Concerns/Enumerates.php | 1 + src/Concerns/IsMagic.php | 35 +++++++++++++ src/Enums.php | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 src/Concerns/IsMagic.php create mode 100644 src/Enums.php diff --git a/src/Concerns/Enumerates.php b/src/Concerns/Enumerates.php index abddd91..95c9251 100644 --- a/src/Concerns/Enumerates.php +++ b/src/Concerns/Enumerates.php @@ -10,5 +10,6 @@ trait Enumerates use CollectsCases; use Compares; use Hydrates; + use IsMagic; use SelfAware; } diff --git a/src/Concerns/IsMagic.php b/src/Concerns/IsMagic.php new file mode 100644 index 0000000..8244e10 --- /dev/null +++ b/src/Concerns/IsMagic.php @@ -0,0 +1,35 @@ + $arguments): mixed + */ + protected static ?Closure $onStaticCall = null; + + /** + * The logic to run when an inaccessible case method is called. + * + * @var ?Closure(object $case, string $name, array $arguments): mixed + */ + protected static ?Closure $onCall = null; + + /** + * The logic to run when a case is invoked. + * + * @var ?Closure(object $case, mixed ...$arguments): mixed + */ + protected static ?Closure $onInvoke = null; + + /** + * Set the logic to run when an inaccessible enum method is called. + * + * @param Closure(class-string $enum, string $name, array $arguments): mixed $callback + */ + public static function onStaticCall(Closure $callback): void + { + static::$onStaticCall = $callback; + } + + /** + * Set the logic to run when an inaccessible case method is called. + * + * @param Closure(object $case, string $name, array $arguments): mixed $callback + */ + public static function onCall(Closure $callback): void + { + static::$onCall = $callback; + } + + /** + * Set the logic to run when a case is invoked. + * + * @param Closure(object $case, mixed ...$arguments): mixed $callback + */ + public static function onInvoke(Closure $callback): void + { + static::$onInvoke = $callback; + } + + /** + * Handle the call to an inaccessible enum method. + * + * @param class-string $enum + * @param array $arguments + */ + public static function handleStaticCall(string $enum, string $name, array $arguments): mixed + { + return static::$onStaticCall + ? (static::$onStaticCall)($enum, $name, $arguments) + : $enum::fromName($name)->value(); + } + + /** + * Handle the call to an inaccessible case method. + * + * @param array $arguments + */ + public static function handleCall(object $case, string $name, array $arguments): mixed + { + return static::$onCall + ? (static::$onCall)($case, $name, $arguments) + : throw new Error(sprintf('Call to undefined method %s::%s()', $case::class, $name)); + } + + /** + * Handle the invocation of a case. + */ + public static function handleInvoke(object $case, mixed ...$arguments): mixed + { + /** @phpstan-ignore method.notFound */ + return static::$onInvoke ? (static::$onInvoke)($case, ...$arguments) : $case->value(); + } +} From aa0b1136f76a165acb5dd444d7da981eab46ca5e Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 30 Sep 2024 11:53:33 -0300 Subject: [PATCH 28/49] Remove add() --- src/CasesCollection.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 6c62255..3b1b991 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -76,16 +76,6 @@ public function toArray(): array return $array; } - /** - * Add cases to the collection. - * - * @param TValue ...$cases - */ - public function add(mixed ...$cases): static - { - return new static([...$this->cases, ...$cases]); - } - /** * Retrieve the first case. * @@ -207,9 +197,11 @@ public function groupBy(callable|string $key): static $grouped = []; foreach ($this->cases as $case) { - $grouped[$case->resolveKey($key)] ??= new static([]); + $grouped[$case->resolveKey($key)][] = $case; + } - $grouped[$case->resolveKey($key)]->add($case); + foreach ($grouped as $key => $cases) { + $grouped[$key] = new static($cases); } return new static($grouped); From 70f1ec5c5bb4308d66b92097356ee031464c97e7 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 30 Sep 2024 11:54:12 -0300 Subject: [PATCH 29/49] Update schema --- phpunit.xml.dist | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 917e093..990c7b1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,28 +1,23 @@ - - - - tests - - - - - src/ - - - - - - - - + + + + + + + + + + + tests + + + + + + + + src/ + + From 9c77fbfdae5c1cd2a9de757074a9e696a0031ba0 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 30 Sep 2024 11:54:22 -0300 Subject: [PATCH 30/49] Ignore PHPUnit cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bd2b70e..c9c70a3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ composer.lock vendor phpcs.xml phpunit.xml +.phpunit.cache .phpunit.result.cache .DS_Store From 60acde4c6a3f7670bf2a0b582c7ec55d64bdda83 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 30 Sep 2024 11:55:54 -0300 Subject: [PATCH 31/49] Update readme --- README.md | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 0ca7ec7..cbfeaa8 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ composer require cerbero/enum * [Elaborating cases](#elaborating-cases) * [Cases collection](#cases-collection) -To supercharge our enums with all functionalities provided by this package, we can simply use the `Enumerates` trait in both pure enums and backed enums: +To supercharge our enums with the features provided by this package, we can let our enums use the `Enumerates` trait: ```php use Cerbero\Enum\Concerns\Enumerates; @@ -108,7 +108,7 @@ Comparisons can also be performed within arrays: ```php PureEnum::one->in(['one', 'four']); // true PureEnum::one->in([1, 4]); // false -PureEnum::one->notIn('one', 'four'); // false +PureEnum::one->notIn(['one', 'four']); // false PureEnum::one->notIn([1, 4]); // true BackedEnum::one->in([1, 4]); // true @@ -120,7 +120,7 @@ BackedEnum::one->notIn(['one', 'four']); // true ### Keys resolution -With the term "key" we refer to any element defined in an enum, such as names, values or methods implemented by cases. Take the following enum for example: +With the term "key" we refer to any element defined for an enum case, such as its name, value or public methods. Take the following enum for example: ```php enum BackedEnum: int @@ -134,35 +134,35 @@ enum BackedEnum: int public function color(): string { return match ($this) { - static::one => 'red', - static::two => 'green', - static::three => 'blue', + self::one => 'red', + self::two => 'green', + self::three => 'blue', }; } public function isOdd(): bool { return match ($this) { - static::one => true, - static::two => false, - static::three => true, + self::one => true, + self::two => false, + self::three => true, }; } } ``` -The keys defined in this enum are `name`, `value` (as it is a backed enum), `color` and `isOdd`. We can retrieve any key assigned to a case by calling `get()`: +The keys defined in this enum are `name`, `value` (as it is a backed enum), `color` and `isOdd`. We can retrieve any key assigned to a case by calling `resolveKey()`: ```php -PureEnum::one->get('name'); // 'one' -PureEnum::one->get('value'); // throws ValueError as it is a pure enum -PureEnum::one->get('color'); // 'red' -PureEnum::one->get(fn(PureEnum $caseOne) => $caseOne->isOdd()); // true - -BackedEnum::one->get('name'); // 'one' -BackedEnum::one->get('value'); // 1 -BackedEnum::one->get('color'); // 'red' -BackedEnum::one->get(fn(BackedEnum $caseOne) => $caseOne->isOdd()); // true +PureEnum::one->resolveKey('name'); // 'one' +PureEnum::one->resolveKey('value'); // throws ValueError as it is a pure enum +PureEnum::one->resolveKey('color'); // 'red' +PureEnum::one->resolveKey(fn(PureEnum $caseOne) => $caseOne->isOdd()); // true + +BackedEnum::one->resolveKey('name'); // 'one' +BackedEnum::one->resolveKey('value'); // 1 +BackedEnum::one->resolveKey('color'); // 'red' +BackedEnum::one->resolveKey(fn(BackedEnum $caseOne) => $caseOne->isOdd()); // true ``` At first glance this method may seem an overkill as "keys" can be accessed directly by cases like this: @@ -233,7 +233,7 @@ PureEnum::casesBy('color'); // ['red' => PureEnum::one, 'green' => PureEnum::two PureEnum::groupBy('color'); // ['red' => [PureEnum::one], 'green' => [PureEnum::two], 'blue' => [PureEnum::three]] PureEnum::names(); // ['one', 'two', 'three'] PureEnum::values(); // [] -PureEnum::pluck(); // ['one', 'two', 'three'] +PureEnum::pluck('name'); // ['one', 'two', 'three'] PureEnum::pluck('color'); // ['red', 'green', 'blue'] PureEnum::pluck(fn(PureEnum $case) => $case->isOdd()); // [true, false, true] PureEnum::pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] @@ -259,7 +259,7 @@ BackedEnum::casesBy('color'); // ['red' => BackedEnum::one, 'green' => BackedEnu BackedEnum::groupBy('color'); // ['red' => [BackedEnum::one], 'green' => [BackedEnum::two], 'blue' => [BackedEnum::three]] BackedEnum::names(); // ['one', 'two', 'three'] BackedEnum::values(); // [1, 2, 3] -BackedEnum::pluck(); // [1, 2, 3] +BackedEnum::pluck('value'); // [1, 2, 3] BackedEnum::pluck('color'); // ['red', 'green', 'blue'] BackedEnum::pluck(fn(BackedEnum $case) => $case->isOdd()); // [true, false, true] BackedEnum::pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] @@ -306,7 +306,7 @@ foreach (PureEnum::collect() as $case) { Obtaining the underlying plain list of cases is easy: ```php -PureEnum::collect()->cases(); // [PureEnum::one, PureEnum::two, PureEnum::three] +PureEnum::collect()->all(); // [PureEnum::one, PureEnum::two, PureEnum::three] ``` Sometimes we may need to extract only the first case of the collection: @@ -318,13 +318,13 @@ PureEnum::filter(fn(PureEnum $case) => !$case->isOdd())->first(); // PureEnum::t For reference, here are all the operations available in `CasesCollection`: ```php -PureEnum::collect()->cases(); // [PureEnum::one, PureEnum::two, PureEnum::three] +PureEnum::collect()->all(); // [PureEnum::one, PureEnum::two, PureEnum::three] PureEnum::collect()->count(); // 3 PureEnum::collect()->first(); // PureEnum::one -PureEnum::collect()->keyByName(); // ['one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three] -PureEnum::collect()->keyByValue(); // [] -PureEnum::collect()->keyBy('color'); // ['red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three] -PureEnum::collect()->groupBy('color'); // ['red' => [PureEnum::one], 'green' => [PureEnum::two], 'blue' => [PureEnum::three]] +PureEnum::collect()->keyByName(); // CasesCollection<'one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three> +PureEnum::collect()->keyByValue(); // CasesCollection<> +PureEnum::collect()->keyBy('color'); // CasesCollection<'red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three> +PureEnum::collect()->groupBy('color'); // CasesCollection<'red' => CasesCollection, 'green' => CasesCollection, 'blue' => CasesCollection> PureEnum::collect()->names(); // ['one', 'two', 'three'] PureEnum::collect()->values(); // [] PureEnum::collect()->pluck(); // ['one', 'two', 'three'] @@ -345,13 +345,13 @@ PureEnum::collect()->sortByDescValue(); // CasesCollection<> PureEnum::collect()->sortBy('color'); // CasesCollection PureEnum::collect()->sortByDesc(fn(PureEnum $case) => $case->color()); // CasesCollection -BackedEnum::collect()->cases(); // [BackedEnum::one, BackedEnum::two, BackedEnum::three] +BackedEnum::collect()->all(); // [BackedEnum::one, BackedEnum::two, BackedEnum::three] BackedEnum::collect()->count(); // 3 BackedEnum::collect()->first(); // BackedEnum::one -BackedEnum::collect()->keyByName(); // ['one' => BackedEnum::one, 'two' => BackedEnum::two, 'three' => BackedEnum::three] -BackedEnum::collect()->keyByValue(); // [1 => BackedEnum::one, 2 => BackedEnum::two, 3 => BackedEnum::three] -BackedEnum::collect()->keyBy('color'); // ['red' => BackedEnum::one, 'green' => BackedEnum::two, 'blue' => BackedEnum::three] -BackedEnum::collect()->groupBy('color'); // ['red' => [BackedEnum::one], 'green' => [BackedEnum::two], 'blue' => [BackedEnum::three]] +BackedEnum::collect()->keyByName(); // CasesCollection<'one' => BackedEnum::one, 'two' => BackedEnum::two, 'three' => BackedEnum::three> +BackedEnum::collect()->keyByValue(); // CasesCollection<1 => BackedEnum::one, 2 => BackedEnum::two, 3 => BackedEnum::three> +BackedEnum::collect()->keyBy('color'); // CasesCollection<'red' => BackedEnum::one, 'green' => BackedEnum::two, 'blue' => BackedEnum::three> +BackedEnum::collect()->groupBy('color'); // CasesCollection<'red' => CasesCollection, 'green' => CasesCollection, 'blue' => CasesCollection> BackedEnum::collect()->names(); // ['one', 'two', 'three'] BackedEnum::collect()->values(); // [1, 2, 3] BackedEnum::collect()->pluck(); // [1, 2, 3] From b176a8e488a026716df0d1d844e8dced03cbd96f Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 30 Sep 2024 11:57:29 -0300 Subject: [PATCH 32/49] Update tests --- tests/BackedEnumTest.php | 101 +++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index f3f1532..f475b8b 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -2,6 +2,7 @@ use Cerbero\Enum\CasesCollection; use Cerbero\Enum\BackedEnum; +use Pest\Expectation; it('determines whether the enum is pure') ->expect(BackedEnum::isPure()) @@ -22,74 +23,94 @@ it('retrieves a collection with all the cases') ->expect(BackedEnum::collect()) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); it('retrieves all cases keyed by name') - ->expect(BackedEnum::casesByName()) + ->expect(BackedEnum::keyByName()) + ->toBeInstanceOf(CasesCollection::class) + ->all() ->toBe(['one' => BackedEnum::one, 'two' => BackedEnum::two, 'three' => BackedEnum::three]); it('retrieves all cases keyed by value') - ->expect(BackedEnum::casesByValue()) + ->expect(BackedEnum::keyByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->all() ->toBe([1 => BackedEnum::one, 2 => BackedEnum::two, 3 => BackedEnum::three]); it('retrieves all cases keyed by a custom key') - ->expect(BackedEnum::casesBy('color')) + ->expect(BackedEnum::keyBy('color')) + ->toBeInstanceOf(CasesCollection::class) + ->all() ->toBe(['red' => BackedEnum::one, 'green' => BackedEnum::two, 'blue' => BackedEnum::three]); it('retrieves all cases keyed by the result of a closure') - ->expect(BackedEnum::casesBy(fn(BackedEnum $case) => $case->shape())) - ->toBe(['triangle' => BackedEnum::one, 'square' => BackedEnum::two, 'circle' => BackedEnum::three]); + ->expect(BackedEnum::keyBy(fn(BackedEnum $case) => $case->shape())) + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $case, Expectation $key) => $key->toBe('triangle')->and($case)->toBe(BackedEnum::one), + fn(Expectation $case, Expectation $key) => $key->toBe('square')->and($case)->toBe(BackedEnum::two), + fn(Expectation $case, Expectation $key) => $key->toBe('circle')->and($case)->toBe(BackedEnum::three), + ); it('retrieves all cases grouped by a custom key', function () { expect(BackedEnum::groupBy('color')) - ->toBe(['red' => [BackedEnum::one], 'green' => [BackedEnum::two], 'blue' => [BackedEnum::three]]); + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $cases, Expectation $key) => $key->toBe('red')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([BackedEnum::one]), + fn(Expectation $cases, Expectation $key) => $key->toBe('green')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([BackedEnum::two]), + fn(Expectation $cases, Expectation $key) => $key->toBe('blue')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([BackedEnum::three]), + ); }); it('retrieves all cases grouped by the result of a closure', function () { expect(BackedEnum::groupBy(fn(BackedEnum $case) => $case->isOdd())) - ->toBe([1 => [BackedEnum::one, BackedEnum::three], 0 => [BackedEnum::two]]); + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $cases) => $cases->toBeInstanceOf(CasesCollection::class)->all()->toBe([BackedEnum::one, BackedEnum::three]), + fn(Expectation $cases) => $cases->toBeInstanceOf(CasesCollection::class)->all()->toBe([BackedEnum::two]), + ); }); it('retrieves a collection with the filtered cases') ->expect(BackedEnum::filter(fn(UnitEnum $case) => $case->name !== 'three')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([BackedEnum::one, BackedEnum::two]); it('retrieves a collection with cases filtered by a key', function () { expect(BackedEnum::filter('isOdd')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one, BackedEnum::three]); + ->all() + ->toBe([0 => BackedEnum::one, 2 => BackedEnum::three]); }); it('retrieves a collection of cases having the given names') ->expect(BackedEnum::only('two', 'three')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two, BackedEnum::three]); + ->all() + ->toBe([1 => BackedEnum::two, 2 => BackedEnum::three]); it('retrieves a collection of cases not having the given names') ->expect(BackedEnum::except('one', 'three')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two]); + ->all() + ->toBe([1 => BackedEnum::two]); it('retrieves a collection of backed cases having the given values') ->expect(BackedEnum::onlyValues(2, 3)) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two, BackedEnum::three]); + ->all() + ->toBe([1 => BackedEnum::two, 2 => BackedEnum::three]); it('retrieves a collection of backed cases not having the given values') ->expect(BackedEnum::exceptValues(1, 3)) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two]); + ->all() + ->toBe([1 => BackedEnum::two]); it('retrieves an array of values') - ->expect(BackedEnum::pluck()) + ->expect(BackedEnum::pluck('value')) ->toBe([1, 2, 3]); it('retrieves an array of custom values') @@ -191,49 +212,49 @@ it('retrieves a collection of cases sorted by name ascending') ->expect(BackedEnum::sort()) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one, BackedEnum::three, BackedEnum::two]); + ->all() + ->toBe([0 => BackedEnum::one, 2 => BackedEnum::three, 1 => BackedEnum::two]); it('retrieves a collection of cases sorted by name descending') ->expect(BackedEnum::sortDesc()) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two, BackedEnum::three, BackedEnum::one]); + ->all() + ->toBe([1 => BackedEnum::two, 2 => BackedEnum::three, 0 => BackedEnum::one]); it('retrieves a collection of cases sorted by value ascending') ->expect(BackedEnum::sortByValue()) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); it('retrieves a collection of cases sorted by value descending') ->expect(BackedEnum::sortByDescValue()) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); + ->all() + ->toBe([2 => BackedEnum::three, 1 => BackedEnum::two, 0 => BackedEnum::one]); it('retrieves a collection of cases sorted by a custom value ascending') ->expect(BackedEnum::sortBy('color')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); + ->all() + ->toBe([2 => BackedEnum::three, 1 => BackedEnum::two, 0 => BackedEnum::one]); it('retrieves a collection of cases sorted by a custom value descending') ->expect(BackedEnum::sortByDesc('color')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); it('retrieves a collection of cases sorted by the result of a closure ascending') ->expect(BackedEnum::sortBy(fn(BackedEnum $case) => $case->shape())) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); + ->all() + ->toBe([2 => BackedEnum::three, 1 => BackedEnum::two, 0 => BackedEnum::one]); it('retrieves a collection of cases sorted by the result of a closure descending') ->expect(BackedEnum::sortByDesc(fn(BackedEnum $case) => $case->shape())) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); it('retrieves the count of cases') @@ -287,7 +308,7 @@ ]); it('retrieves the cases hydrated from a key') - ->expect(fn(string $key, mixed $value, array $cases) => BackedEnum::fromKey($key, $value)->cases() === $cases) + ->expect(fn(string $key, mixed $value, array $cases) => BackedEnum::fromKey($key, $value)->all() === $cases) ->toBeTrue() ->with([ ['color', 'red', [BackedEnum::one]], @@ -298,14 +319,14 @@ it('retrieves the cases hydrated from a key using a closure') ->expect(BackedEnum::fromKey(fn(BackedEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([BackedEnum::two]); it('throws a value error when hydrating cases with an invalid key', fn() => BackedEnum::fromKey('color', 'orange')) ->throws(ValueError::class, 'Invalid value for the key "color" for enum "Cerbero\Enum\BackedEnum"'); it('retrieves the case hydrated from a key or returns null') - ->expect(fn(string $key, mixed $value, ?array $cases) => BackedEnum::tryFromKey($key, $value)?->cases() === $cases) + ->expect(fn(string $key, mixed $value, ?array $cases) => BackedEnum::tryFromKey($key, $value)?->all() === $cases) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ @@ -318,11 +339,11 @@ it('attempts to retrieve the case hydrated from a key using a closure') ->expect(BackedEnum::tryFromKey(fn(BackedEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([BackedEnum::two]); it('retrieves the key of a case') - ->expect(fn(string $key, mixed $value) => BackedEnum::one->get($key) === $value) + ->expect(fn(string $key, mixed $value) => BackedEnum::one->resolveKey($key) === $value) ->toBeTrue() ->with([ ['name', 'one'], @@ -332,8 +353,8 @@ ]); it('retrieves the key of a case using a closure') - ->expect(BackedEnum::one->get(fn(BackedEnum $case) => $case->color())) + ->expect(BackedEnum::one->resolveKey(fn(BackedEnum $case) => $case->color())) ->toBe('red'); -it('throws a value error when attempting to retrieve an invalid key', fn() => BackedEnum::one->get('invalid')) +it('throws a value error when attempting to retrieve an invalid key', fn() => BackedEnum::one->resolveKey('invalid')) ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\BackedEnum"'); From 387bd9026ae574abe2bdbe3ebafd6fe643557a78 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 30 Sep 2024 11:57:56 -0300 Subject: [PATCH 33/49] Update tests --- tests/PureEnumTest.php | 78 ++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index 83a947c..8da0ad5 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -2,6 +2,7 @@ use Cerbero\Enum\CasesCollection; use Cerbero\Enum\PureEnum; +use Pest\Expectation; it('determines whether the enum is pure') ->expect(PureEnum::isPure()) @@ -14,37 +15,46 @@ it('retrieves a collection with all the cases') ->expect(PureEnum::collect()) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); it('retrieves all cases keyed by name', function () { - expect(PureEnum::casesByName()) + expect(PureEnum::keyByName()) ->toBe(['one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three]); }); it('retrieves all cases keyed by value', function () { - expect(PureEnum::casesByValue()) + expect(PureEnum::keyByValue()) ->toBeEmpty(); }); it('retrieves all cases keyed by a custom key', function () { - expect(PureEnum::casesBy('color')) + expect(PureEnum::keyBy('color')) ->toBe(['red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three]); }); it('retrieves all cases keyed by the result of a closure', function () { - expect(PureEnum::casesBy(fn(PureEnum $case) => $case->shape())) + expect(PureEnum::keyBy(fn(PureEnum $case) => $case->shape())) ->toBe(['triangle' => PureEnum::one, 'square' => PureEnum::two, 'circle' => PureEnum::three]); }); it('retrieves all cases grouped by a custom key', function () { expect(PureEnum::groupBy('color')) - ->toBe(['red' => [PureEnum::one], 'green' => [PureEnum::two], 'blue' => [PureEnum::three]]); + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $cases, Expectation $key) => $key->toBe('red')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::one]), + fn(Expectation $cases, Expectation $key) => $key->toBe('green')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::two]), + fn(Expectation $cases, Expectation $key) => $key->toBe('blue')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::three]), + ); }); it('retrieves all cases grouped by the result of a closure', function () { expect(PureEnum::groupBy(fn(PureEnum $case) => $case->isOdd())) - ->toBe([1 => [PureEnum::one, PureEnum::three], 0 => [PureEnum::two]]); + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $cases) => $cases->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::one, PureEnum::three]), + fn(Expectation $cases) => $cases->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::two]), + ); }); it('retrieves all the names of the cases', function () { @@ -58,47 +68,47 @@ it('retrieves a collection with the filtered cases', function () { expect(PureEnum::filter(fn(UnitEnum $case) => $case->name !== 'three')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([PureEnum::one, PureEnum::two]); }); it('retrieves a collection with cases filtered by a key', function () { expect(PureEnum::filter('isOdd')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); + ->all() + ->toBe([0 => PureEnum::one, 2 => PureEnum::three]); }); it('retrieves a collection of cases having the given names', function () { expect(PureEnum::only('two', 'three')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two, PureEnum::three]); + ->all() + ->toBe([1 => PureEnum::two, 2 => PureEnum::three]); }); it('retrieves a collection of cases not having the given names', function () { expect(PureEnum::except('one', 'three')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two]); + ->all() + ->toBe([1 => PureEnum::two]); }); it('retrieves a collection of backed cases having the given values', function () { expect(PureEnum::onlyValues(2, 3)) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBeEmpty(); }); it('retrieves a collection of backed cases not having the given values', function () { expect(PureEnum::exceptValues(1, 3)) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBeEmpty(); }); -it('retrieves an array of values', function () { - expect(PureEnum::pluck())->toBe(['one', 'two', 'three']); +it('retrieves an array of names', function () { + expect(PureEnum::pluck('name'))->toBe(['one', 'two', 'three']); }); it('retrieves an array of custom values', function () { @@ -202,56 +212,56 @@ it('retrieves a collection of cases sorted by name ascending', function () { expect(PureEnum::sort()) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three, PureEnum::two]); + ->all() + ->toBe([0 => PureEnum::one, 2 => PureEnum::three, 1 => PureEnum::two]); }); it('retrieves a collection of cases sorted by name descending', function () { expect(PureEnum::sortDesc()) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two, PureEnum::three, PureEnum::one]); + ->all() + ->toBe([1 => PureEnum::two, 2 => PureEnum::three, 0 => PureEnum::one]); }); it('retrieves a collection of cases sorted by value ascending', function () { expect(PureEnum::sortByValue()) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBeEmpty(); }); it('retrieves a collection of cases sorted by value descending', function () { expect(PureEnum::sortByDescValue()) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBeEmpty(); }); it('retrieves a collection of cases sorted by a custom value ascending', function () { expect(PureEnum::sortBy('color')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::three, PureEnum::two, PureEnum::one]); + ->all() + ->toBe([2 => PureEnum::three, 1 => PureEnum::two, 0 => PureEnum::one]); }); it('retrieves a collection of cases sorted by a custom value descending', function () { expect(PureEnum::sortByDesc('color')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); }); it('retrieves a collection of cases sorted by the result of a closure ascending', function () { expect(PureEnum::sortBy(fn(PureEnum $case) => $case->shape())) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::three, PureEnum::two, PureEnum::one]); + ->all() + ->toBe([2 => PureEnum::three, 1 => PureEnum::two, 0 => PureEnum::one]); }); it('retrieves a collection of cases sorted by the result of a closure descending', function () { expect(PureEnum::sortByDesc(fn(PureEnum $case) => $case->shape())) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); }); @@ -306,7 +316,7 @@ ]); it('retrieves the cases hydrated from a key') - ->expect(fn(string $key, mixed $value, array $cases) => PureEnum::fromKey($key, $value)->cases() === $cases) + ->expect(fn(string $key, mixed $value, array $cases) => PureEnum::fromKey($key, $value)->all() === $cases) ->toBeTrue() ->with([ ['color', 'red', [PureEnum::one]], @@ -317,14 +327,14 @@ it('retrieves the cases hydrated from a key using a closure') ->expect(PureEnum::fromKey(fn(PureEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([PureEnum::two]); it('throws a value error when hydrating cases with an invalid key', fn() => PureEnum::fromKey('color', 'orange')) ->throws(ValueError::class, 'Invalid value for the key "color" for enum "Cerbero\Enum\PureEnum"'); it('retrieves the case hydrated from a key or returns null') - ->expect(fn(string $key, mixed $value, ?array $cases) => PureEnum::tryFromKey($key, $value)?->cases() === $cases) + ->expect(fn(string $key, mixed $value, ?array $cases) => PureEnum::tryFromKey($key, $value)?->all() === $cases) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ @@ -337,7 +347,7 @@ it('attempts to retrieve the case hydrated from a key using a closure') ->expect(PureEnum::tryFromKey(fn(PureEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([PureEnum::two]); it('retrieves the key of a case') From cc54f8daf9a8fa293fb80a66ed9f2ac7288ac5f4 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 1 Oct 2024 12:49:20 -0300 Subject: [PATCH 34/49] Update tests --- tests/CasesCollectionTest.php | 103 +++++++++++++++++++--------------- tests/PureEnumTest.php | 11 +++- 2 files changed, 68 insertions(+), 46 deletions(-) diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php index 573b232..87d080c 100644 --- a/tests/CasesCollectionTest.php +++ b/tests/CasesCollectionTest.php @@ -3,10 +3,11 @@ use Cerbero\Enum\BackedEnum; use Cerbero\Enum\CasesCollection; use Cerbero\Enum\PureEnum; +use Pest\Expectation; it('retrieves all the cases') ->expect(new CasesCollection(PureEnum::cases())) - ->cases() + ->all() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); it('retrieves the count of all the cases') @@ -29,29 +30,32 @@ ->first() ->toBeNull(); -it('returns a default value if no case is present') - ->expect(new CasesCollection([])) - ->first(default: PureEnum::one) - ->toBe(PureEnum::one); - it('retrieves the cases keyed by name') - ->expect(new CasesCollection(PureEnum::cases())) - ->keyByName() + ->expect((new CasesCollection(PureEnum::cases()))->keyByName()) + ->toBeInstanceOf(CasesCollection::class) + ->all() ->toBe(['one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three]); it('retrieves the cases keyed by a custom key') - ->expect(new CasesCollection(PureEnum::cases())) - ->keyBy('color') + ->expect((new CasesCollection(PureEnum::cases()))->keyBy('color')) + ->toBeInstanceOf(CasesCollection::class) + ->all() ->toBe(['red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three]); it('retrieves the cases keyed by a custom closure') ->expect(new CasesCollection(PureEnum::cases())) ->keyBy(fn(PureEnum $case) => $case->shape()) - ->toBe(['triangle' => PureEnum::one, 'square' => PureEnum::two, 'circle' => PureEnum::three]); - -it('retrieves the cases keyed by value') - ->expect(new CasesCollection(BackedEnum::cases())) - ->keyByValue() + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $case, Expectation $key) => $key->toBe('triangle')->and($case)->toBe(PureEnum::one), + fn(Expectation $case, Expectation $key) => $key->toBe('square')->and($case)->toBe(PureEnum::two), + fn(Expectation $case, Expectation $key) => $key->toBe('circle')->and($case)->toBe(PureEnum::three), + ); + +it('retrieves the cases keyed by value xxxxxxx') + ->expect((new CasesCollection(BackedEnum::cases()))->keyByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->all() ->toBe([1 => BackedEnum::one, 2 => BackedEnum::two, 3 => BackedEnum::three]); it('retrieves an empty array when trying to key cases by value belonging to a pure enum') @@ -62,12 +66,21 @@ it('retrieves the cases grouped by a custom key') ->expect(new CasesCollection(PureEnum::cases())) ->groupBy('color') - ->toBe(['red' => [PureEnum::one], 'green' => [PureEnum::two], 'blue' => [PureEnum::three]]); + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $cases, Expectation $key) => $key->toBe('red')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::one]), + fn(Expectation $cases, Expectation $key) => $key->toBe('green')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::two]), + fn(Expectation $cases, Expectation $key) => $key->toBe('blue')->and($cases)->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::three]), + ); it('retrieves the cases grouped by a custom closure') ->expect(new CasesCollection(PureEnum::cases())) ->groupBy(fn(PureEnum $case) => $case->isOdd()) - ->toBe([1 => [PureEnum::one, PureEnum::three], 0 => [PureEnum::two]]); + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $cases) => $cases->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::one, PureEnum::three]), + fn(Expectation $cases) => $cases->toBeInstanceOf(CasesCollection::class)->all()->toBe([PureEnum::two]), + ); it('retrieves all the names of the cases') ->expect(new CasesCollection(PureEnum::cases())) @@ -84,14 +97,14 @@ ->values() ->toBeEmpty(); -it('retrieves a list of names by default when plucking a pure enum') +it('retrieves a list of names') ->expect(new CasesCollection(PureEnum::cases())) - ->pluck() + ->pluck('name') ->toBe(['one', 'two', 'three']); -it('retrieves a list of names by default when plucking a backed enum') +it('retrieves a list of values') ->expect(new CasesCollection(BackedEnum::cases())) - ->pluck() + ->pluck('value') ->toBe([1, 2, 3]); it('retrieves a list of custom values when plucking with an argument') @@ -117,99 +130,99 @@ it('retrieves a collection with filtered cases', function () { expect((new CasesCollection(PureEnum::cases()))->filter(fn(PureEnum $case) => $case->isOdd())) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); + ->all() + ->toBe([0 => PureEnum::one, 2 => PureEnum::three]); }); it('retrieves a collection with cases filtered by a key', function () { expect((new CasesCollection(PureEnum::cases()))->filter('isOdd')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); + ->all() + ->toBe([0 => PureEnum::one, 2 => PureEnum::three]); }); it('retrieves a collection of cases with the given names', function () { expect((new CasesCollection(PureEnum::cases()))->only('one', 'three')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); + ->all() + ->toBe([0 => PureEnum::one, 2 => PureEnum::three]); }); it('retrieves a collection of cases excluding the given names', function () { expect((new CasesCollection(PureEnum::cases()))->except('one', 'three')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two]); + ->all() + ->toBe([1 => PureEnum::two]); }); it('retrieves a collection of cases with the given values', function () { expect((new CasesCollection(BackedEnum::cases()))->onlyValues(1, 3)) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one, BackedEnum::three]); + ->all() + ->toBe([0 => BackedEnum::one, 2 => BackedEnum::three]); }); it('retrieves a collection of cases excluding the given values', function () { expect((new CasesCollection(BackedEnum::cases()))->exceptValues(1, 3)) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two]); + ->all() + ->toBe([1 => BackedEnum::two]); }); it('retrieves an empty collection of cases when when including values of pure enums', function () { expect((new CasesCollection(PureEnum::cases()))->onlyValues(1, 3)) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBeEmpty(); }); it('retrieves an empty collection of cases when when excluding values of pure enums', function () { expect((new CasesCollection(PureEnum::cases()))->exceptValues(1, 3)) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBeEmpty(); }); it('retrieves a collection of cases sorted by name ascending', function () { expect((new CasesCollection(PureEnum::cases()))->sort()) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three, PureEnum::two]); + ->all() + ->toBe([0 => PureEnum::one, 2 => PureEnum::three, 1 => PureEnum::two]); }); it('retrieves a collection of cases sorted by name decending', function () { expect((new CasesCollection(PureEnum::cases()))->sortDesc()) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two, PureEnum::three, PureEnum::one]); + ->all() + ->toBe([1 => PureEnum::two, 2 => PureEnum::three, 0 => PureEnum::one]); }); it('retrieves a collection of cases sorted by a key ascending', function () { expect((new CasesCollection(PureEnum::cases()))->sortBy('color')) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::three, PureEnum::two, PureEnum::one]); + ->all() + ->toBe([2 => PureEnum::three, 1 => PureEnum::two, 0 => PureEnum::one]); }); it('retrieves a collection of cases sorted by a key decending', function () { expect((new CasesCollection(PureEnum::cases()))->sortByDesc('color')) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); }); it('retrieves a collection of cases sorted by value ascending', function () { expect((new CasesCollection(BackedEnum::cases()))->sortByValue()) ->toBeInstanceOf(CasesCollection::class) - ->cases() + ->all() ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); }); it('retrieves a collection of cases sorted by value decending', function () { expect((new CasesCollection(BackedEnum::cases()))->sortByDescValue()) ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); + ->all() + ->toBe([2 => BackedEnum::three, 1 => BackedEnum::two, 0 => BackedEnum::one]); }); it('retrieves the iterator', function () { diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index 8da0ad5..3c30995 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -20,6 +20,8 @@ it('retrieves all cases keyed by name', function () { expect(PureEnum::keyByName()) + ->toBeInstanceOf(CasesCollection::class) + ->all() ->toBe(['one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three]); }); @@ -30,12 +32,19 @@ it('retrieves all cases keyed by a custom key', function () { expect(PureEnum::keyBy('color')) + ->toBeInstanceOf(CasesCollection::class) + ->all() ->toBe(['red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three]); }); it('retrieves all cases keyed by the result of a closure', function () { expect(PureEnum::keyBy(fn(PureEnum $case) => $case->shape())) - ->toBe(['triangle' => PureEnum::one, 'square' => PureEnum::two, 'circle' => PureEnum::three]); + ->toBeInstanceOf(CasesCollection::class) + ->sequence( + fn(Expectation $case, Expectation $key) => $key->toBe('triangle')->and($case)->toBe(PureEnum::one), + fn(Expectation $case, Expectation $key) => $key->toBe('square')->and($case)->toBe(PureEnum::two), + fn(Expectation $case, Expectation $key) => $key->toBe('circle')->and($case)->toBe(PureEnum::three), + ); }); it('retrieves all cases grouped by a custom key', function () { From 7aa76990cfb9e3c38f0f45b83efbf8854db4d9a1 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 1 Oct 2024 13:06:23 -0300 Subject: [PATCH 35/49] Fix error message based on PHP version --- tests/BackedEnumTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index f475b8b..8c89c1f 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -271,7 +271,8 @@ ]); it('throws a value error when hydrating backed cases with a missing value', fn() => BackedEnum::from(4)) - ->throws(ValueError::class, '4 is not a valid backing value for enum "Cerbero\Enum\BackedEnum"'); + ->throwsIf(version_compare(PHP_VERSION, '8.2') == -1, ValueError::class, '4 is not a valid backing value for enum "Cerbero\Enum\BackedEnum"') + ->throwsIf(version_compare(PHP_VERSION, '8.2') >= 0, ValueError::class, '4 is not a valid backing value for enum Cerbero\Enum\BackedEnum'); it('retrieves the case hydrated from a value or returns null') ->expect(fn(int $value, ?BackedEnum $case) => BackedEnum::tryFrom($value) === $case) From 7ed5efb199d3a1c8962b04f529f1c7261610b17a Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 3 Oct 2024 12:23:31 -0300 Subject: [PATCH 36/49] Update error message --- src/Enums.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Enums.php b/src/Enums.php index 18c29df..7d0e202 100644 --- a/src/Enums.php +++ b/src/Enums.php @@ -84,8 +84,8 @@ public static function handleStaticCall(string $enum, string $name, array $argum public static function handleCall(object $case, string $name, array $arguments): mixed { return static::$onCall - ? (static::$onCall)($case, $name, $arguments) - : throw new Error(sprintf('Call to undefined method %s::%s()', $case::class, $name)); + ? (static::$onCall)($case, $name, $arguments) /** @phpstan-ignore-next-line property.notFound */ + : throw new Error(sprintf('Call to undefined method %s::%s->%s()', $case::class, $case->name, $name)); } /** From 637ed3c4caa7dd6ff10777b0869332ebb0a52ff8 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 3 Oct 2024 12:23:41 -0300 Subject: [PATCH 37/49] Update tests --- tests/BackedEnumTest.php | 85 +++++++++++++++++++++++++++++++++++ tests/CasesCollectionTest.php | 10 +++++ tests/PureEnumTest.php | 85 +++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index 8c89c1f..bee3690 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -2,6 +2,7 @@ use Cerbero\Enum\CasesCollection; use Cerbero\Enum\BackedEnum; +use Cerbero\Enum\Enums; use Pest\Expectation; it('determines whether the enum is pure') @@ -16,6 +17,27 @@ ->expect(BackedEnum::names()) ->toBe(['one', 'two', 'three']); +it('retrieves the first case', fn() => expect(BackedEnum::first())->toBe(BackedEnum::one)); + +it('retrieves the first case with a closure') + ->expect(BackedEnum::first(fn(BackedEnum $case) => !$case->isOdd())) + ->toBe(BackedEnum::two); + +it('retrieves the result of mapping over all the cases', function() { + $cases = $keys = []; + + $mapped = BackedEnum::map(function(BackedEnum $case, int $key) use (&$cases, &$keys) { + $cases[] = $case; + $keys[] = $key; + + return $case->color(); + }); + + expect($cases)->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]) + ->and($keys)->toBe([0, 1, 2]) + ->and($mapped)->toBe(['red', 'green', 'blue']); +}); + it('retrieves all the values of the backed cases') ->expect(BackedEnum::values()) ->toBe([1, 2, 3]); @@ -343,6 +365,65 @@ ->all() ->toBe([BackedEnum::two]); +it('handles the call to an inaccessible enum method') + ->expect(BackedEnum::one()) + ->toBe(1); + +it('fails handling the call to an invalid enum method', fn() => BackedEnum::four()) + ->throws(ValueError::class, '"four" is not a valid name for enum "Cerbero\Enum\BackedEnum"'); + +it('runs custom logic when calling an inaccessible enum method', function() { + Enums::onStaticCall(function(string $enum, string $name, array $arguments) { + expect($enum)->toBe(BackedEnum::class) + ->and($name)->toBe('unknownStaticMethod') + ->and($arguments)->toBe([1, 2, 3]); + + return 'ciao'; + }); + + expect(BackedEnum::unknownStaticMethod(1, 2, 3))->toBe('ciao'); + + (fn() => static::$onStaticCall = null)->bindTo(null, Enums::class)(); +}); + +it('handles the call to an inaccessible case method', fn() => BackedEnum::one->unknownMethod()) + ->throws(Error::class, 'Call to undefined method Cerbero\Enum\BackedEnum::one->unknownMethod()'); + +it('runs custom logic when calling an inaccessible case method', function() { + Enums::onCall(function(object $case, string $name, array $arguments) { + expect($case)->toBeInstanceOf(BackedEnum::class) + ->and($name)->toBe('unknownMethod') + ->and($arguments)->toBe([1, 2, 3]); + + return 'ciao'; + }); + + expect(BackedEnum::one->unknownMethod(1, 2, 3))->toBe('ciao'); + + (fn() => self::$onCall = null)->bindTo(null, Enums::class)(); +}); + +it('handles the invocation of a case') + ->expect((BackedEnum::one)()) + ->toBe(1); + +it('runs custom logic when invocating a case', function() { + Enums::onInvoke(function(object $case, mixed ...$arguments) { + expect($case)->toBeInstanceOf(BackedEnum::class) + ->and($arguments)->toBe([1, 2, 3]); + + return 'ciao'; + }); + + expect((BackedEnum::one)(1, 2, 3))->toBe('ciao'); + + (fn() => self::$onInvoke = null)->bindTo(null, Enums::class)(); +}); + +it('retrieves the keys of an enum', function() { + expect(BackedEnum::keys())->toBe(['name', 'value', 'color', 'shape', 'isOdd']); +}); + it('retrieves the key of a case') ->expect(fn(string $key, mixed $value) => BackedEnum::one->resolveKey($key) === $value) ->toBeTrue() @@ -359,3 +440,7 @@ it('throws a value error when attempting to retrieve an invalid key', fn() => BackedEnum::one->resolveKey('invalid')) ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\BackedEnum"'); + +it('retrieves the value of a backed case or the name of a pure case', function() { + expect(BackedEnum::one->value())->toBe(1); +}); diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php index 87d080c..54e7bd7 100644 --- a/tests/CasesCollectionTest.php +++ b/tests/CasesCollectionTest.php @@ -15,6 +15,11 @@ ->count() ->toBe(3); +it('retrieves all the cases as a plain array recursively') + ->expect((new CasesCollection(PureEnum::cases()))->groupBy('isOdd')) + ->toArray() + ->toBe([1 => [PureEnum::one, PureEnum::three], 0 => [PureEnum::two]]); + it('retrieves the first case') ->expect(new CasesCollection(PureEnum::cases())) ->first() @@ -30,6 +35,11 @@ ->first() ->toBeNull(); +it('retrieves the result of mapping over the cases') + ->expect(new CasesCollection(PureEnum::cases())) + ->map(fn(PureEnum $case) => $case->color()) + ->toBe(['red', 'green', 'blue']); + it('retrieves the cases keyed by name') ->expect((new CasesCollection(PureEnum::cases()))->keyByName()) ->toBeInstanceOf(CasesCollection::class) diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index 3c30995..ffdfecc 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -1,6 +1,7 @@ all() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); +it('retrieves the first case', fn() => expect(PureEnum::first())->toBe(PureEnum::one)); + +it('retrieves the first case with a closure') + ->expect(PureEnum::first(fn(PureEnum $case) => !$case->isOdd())) + ->toBe(PureEnum::two); + +it('retrieves the result of mapping over all the cases', function() { + $cases = $keys = []; + + $mapped = PureEnum::map(function(PureEnum $case, int $key) use (&$cases, &$keys) { + $cases[] = $case; + $keys[] = $key; + + return $case->color(); + }); + + expect($cases)->toBe([PureEnum::one, PureEnum::two, PureEnum::three]) + ->and($keys)->toBe([0, 1, 2]) + ->and($mapped)->toBe(['red', 'green', 'blue']); +}); + it('retrieves all cases keyed by name', function () { expect(PureEnum::keyByName()) ->toBeInstanceOf(CasesCollection::class) @@ -359,6 +381,65 @@ ->all() ->toBe([PureEnum::two]); +it('handles the call to an inaccessible enum method') + ->expect(PureEnum::one()) + ->toBe('one'); + +it('fails handling the call to an invalid enum method', fn() => PureEnum::four()) + ->throws(ValueError::class, '"four" is not a valid name for enum "Cerbero\Enum\PureEnum"'); + +it('runs custom logic when calling an inaccessible enum method', function() { + Enums::onStaticCall(function(string $enum, string $name, array $arguments) { + expect($enum)->toBe(PureEnum::class) + ->and($name)->toBe('unknownStaticMethod') + ->and($arguments)->toBe([1, 2, 3]); + + return 'ciao'; + }); + + expect(PureEnum::unknownStaticMethod(1, 2, 3))->toBe('ciao'); + + (fn() => self::$onStaticCall = null)->bindTo(null, Enums::class)(); +}); + +it('handles the call to an inaccessible case method', fn() => PureEnum::one->unknownMethod()) + ->throws(Error::class, 'Call to undefined method Cerbero\Enum\PureEnum::one->unknownMethod()'); + +it('runs custom logic when calling an inaccessible case method', function() { + Enums::onCall(function(object $case, string $name, array $arguments) { + expect($case)->toBeInstanceOf(PureEnum::class) + ->and($name)->toBe('unknownMethod') + ->and($arguments)->toBe([1, 2, 3]); + + return 'ciao'; + }); + + expect(PureEnum::one->unknownMethod(1, 2, 3))->toBe('ciao'); + + (fn() => self::$onCall = null)->bindTo(null, Enums::class)(); +}); + +it('handles the invocation of a case') + ->expect((PureEnum::one)()) + ->toBe('one'); + +it('runs custom logic when invocating a case', function() { + Enums::onInvoke(function(object $case, mixed ...$arguments) { + expect($case)->toBeInstanceOf(PureEnum::class) + ->and($arguments)->toBe([1, 2, 3]); + + return 'ciao'; + }); + + expect((PureEnum::one)(1, 2, 3))->toBe('ciao'); + + (fn() => self::$onInvoke = null)->bindTo(null, Enums::class)(); +}); + +it('retrieves the keys of an enum', function() { + expect(PureEnum::keys())->toBe(['name', 'color', 'shape', 'isOdd']); +}); + it('retrieves the key of a case') ->expect(fn(string $key, mixed $value) => PureEnum::one->resolveKey($key) === $value) ->toBeTrue() @@ -374,3 +455,7 @@ it('throws a value error when attempting to retrieve an invalid key', fn() => PureEnum::one->resolveKey('invalid')) ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); + +it('retrieves the value of a backed case or the name of a pure case', function() { + expect(PureEnum::one->value())->toBe('one'); +}); From 8ee6742298310522396a2d789eb442c2f7b40cd7 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Thu, 3 Oct 2024 14:59:46 -0300 Subject: [PATCH 38/49] Default value true is true when hydrating from keys --- src/Concerns/Hydrates.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Concerns/Hydrates.php b/src/Concerns/Hydrates.php index 630ad12..4aedfc1 100644 --- a/src/Concerns/Hydrates.php +++ b/src/Concerns/Hydrates.php @@ -65,7 +65,7 @@ public static function tryFrom(string $name): ?static * @return CasesCollection * @throws ValueError */ - public static function fromKey(callable|string $key, mixed $value): CasesCollection + public static function fromKey(callable|string $key, mixed $value = true): CasesCollection { if ($cases = self::tryFromKey($key, $value)) { return $cases; @@ -82,7 +82,7 @@ public static function fromKey(callable|string $key, mixed $value): CasesCollect * @param (callable(self): mixed)|string $key * @return ?CasesCollection */ - public static function tryFromKey(callable|string $key, mixed $value): ?CasesCollection + public static function tryFromKey(callable|string $key, mixed $value = true): ?CasesCollection { $cases = []; From 08017b95f9ae6fa2bf92ae90d61e66e018610502 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Fri, 4 Oct 2024 16:43:21 -0300 Subject: [PATCH 39/49] Update readme --- README.md | 378 +++++++++++++++++++++++------------------------------- 1 file changed, 157 insertions(+), 221 deletions(-) diff --git a/README.md b/README.md index cbfeaa8..b5b6948 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,15 @@ composer require cerbero/enum ## ๐Ÿ”ฎ Usage -* [Classification](#classification) -* [Comparison](#comparison) -* [Keys resolution](#keys-resolution) -* [Hydration](#hydration) -* [Elaborating cases](#elaborating-cases) -* [Cases collection](#cases-collection) +* [โš–๏ธ Comparison](#-comparison) +* [๐Ÿ”‘ Keys](#-keys) +* [๐Ÿšฐ Hydration](#-hydration) +* [๐ŸŽฒ Enum operations](#-enum-operations) +* [๐Ÿงบ Cases collection](#-cases-collection) +* [๐Ÿช„ Magic](#-magic) +* [๐Ÿคณ Self-awareness](#-self-awareness) -To supercharge our enums with the features provided by this package, we can let our enums use the `Enumerates` trait: +To supercharge our enums with all the features provided by this package, we can let our enums use the `Enumerates` trait: ```php use Cerbero\Enum\Concerns\Enumerates; @@ -40,43 +41,30 @@ enum PureEnum { use Enumerates; - case one; - case two; - case three; + case One; + case Two; + case Three; } enum BackedEnum: int { use Enumerates; - case one = 1; - case two = 2; - case three = 3; + case One = 1; + case Two = 2; + case Three = 3; } ``` -### Classification +### โš–๏ธ Comparison -These methods determine whether an enum is pure or backed: +We can check whether an enum includes some names or values. Pure enums check for names and backed enums check for values: ```php -PureEnum::isPure(); // true -PureEnum::isBacked(); // false - -BackedEnum::isPure(); // false -BackedEnum::isBacked(); // true -``` - - -### Comparison - -We can check whether an enum includes some names or values. Pure enums check for names, whilst backed enums check for values: - -```php -PureEnum::has('one'); // true +PureEnum::has('One'); // true PureEnum::has('four'); // false -PureEnum::doesntHave('one'); // false +PureEnum::doesntHave('One'); // false PureEnum::doesntHave('four'); // true BackedEnum::has(1); // true @@ -85,217 +73,207 @@ BackedEnum::doesntHave(1); // false BackedEnum::doesntHave(4); // true ``` -Otherwise we can let cases determine whether they match with a name or a value: +Otherwise we can check whether cases match with a given name or value: ```php -PureEnum::one->is('one'); // true -PureEnum::one->is(1); // false -PureEnum::one->is('four'); // false -PureEnum::one->isNot('one'); // false -PureEnum::one->isNot(1); // true -PureEnum::one->isNot('four'); // true - -BackedEnum::one->is(1); // true -BackedEnum::one->is('1'); // false -BackedEnum::one->is(4); // false -BackedEnum::one->isNot(1); // false -BackedEnum::one->isNot('1'); // true -BackedEnum::one->isNot(4); // true +PureEnum::One->is('One'); // true +PureEnum::One->is(1); // false +PureEnum::One->is('four'); // false +PureEnum::One->isNot('One'); // false +PureEnum::One->isNot(1); // true +PureEnum::One->isNot('four'); // true + +BackedEnum::One->is(1); // true +BackedEnum::One->is('1'); // false +BackedEnum::One->is(4); // false +BackedEnum::One->isNot(1); // false +BackedEnum::One->isNot('1'); // true +BackedEnum::One->isNot(4); // true ``` -Comparisons can also be performed within arrays: +Comparisons can also be performed against arrays: ```php -PureEnum::one->in(['one', 'four']); // true -PureEnum::one->in([1, 4]); // false -PureEnum::one->notIn(['one', 'four']); // false -PureEnum::one->notIn([1, 4]); // true - -BackedEnum::one->in([1, 4]); // true -BackedEnum::one->in(['one', 'four']); // false -BackedEnum::one->notIn([1, 4]); // false -BackedEnum::one->notIn(['one', 'four']); // true +PureEnum::One->in(['One', 'four']); // true +PureEnum::One->in([1, 4]); // false +PureEnum::One->notIn(['One', 'four']); // false +PureEnum::One->notIn([1, 4]); // true + +BackedEnum::One->in([1, 4]); // true +BackedEnum::One->in(['One', 'four']); // false +BackedEnum::One->notIn([1, 4]); // false +BackedEnum::One->notIn(['One', 'four']); // true ``` -### Keys resolution +### ๐Ÿ”‘ Keys -With the term "key" we refer to any element defined for an enum case, such as its name, value or public methods. Take the following enum for example: +With the term "key" we refer to any element defined for an enum case, such as its name, value or public methods: ```php enum BackedEnum: int { use Enumerates; - case one = 1; - case two = 2; - case three = 3; + case One = 1; + case Two = 2; + case Three = 3; public function color(): string { return match ($this) { - self::one => 'red', - self::two => 'green', - self::three => 'blue', + self::One => 'red', + self::Two => 'green', + self::Three => 'blue', }; } public function isOdd(): bool { - return match ($this) { - self::one => true, - self::two => false, - self::three => true, - }; + return $this->value % 2 != 0; } } ``` -The keys defined in this enum are `name`, `value` (as it is a backed enum), `color` and `isOdd`. We can retrieve any key assigned to a case by calling `resolveKey()`: +We can see the keys belonging to an enum by calling the `keys()` method: ```php -PureEnum::one->resolveKey('name'); // 'one' -PureEnum::one->resolveKey('value'); // throws ValueError as it is a pure enum -PureEnum::one->resolveKey('color'); // 'red' -PureEnum::one->resolveKey(fn(PureEnum $caseOne) => $caseOne->isOdd()); // true - -BackedEnum::one->resolveKey('name'); // 'one' -BackedEnum::one->resolveKey('value'); // 1 -BackedEnum::one->resolveKey('color'); // 'red' -BackedEnum::one->resolveKey(fn(BackedEnum $caseOne) => $caseOne->isOdd()); // true +// ['name', 'value', 'color', 'isOdd'] +$keys = BackedEnum::keys(); ``` -At first glance this method may seem an overkill as "keys" can be accessed directly by cases like this: - -```php -BackedEnum::one->name; // 'one' -BackedEnum::one->value; // 1 -BackedEnum::one->color(); // 'red' -BackedEnum::one->isOdd(); // true -``` +Keys provide extra information for an enum and can also be leveraged for the [hydration](#-hydration), [elaboration](#-enum-operations) and [collection](#-cases-collection) of cases. -However `get()` is useful to resolve keys dynamically as a key may be a property, a method or a closure. It often gets called internally for more advanced functionalities that we are going to explore very soon. +### ๐Ÿšฐ Hydration -### Hydration - -An enum case can be instantiated from its own name, value (if backed) and [keys](#keys-resolution): +An enum case can be instantiated from its own name, value (if backed) or [keys](#-keys): ```php -PureEnum::from('one'); // PureEnum::one +PureEnum::from('One'); // PureEnum::One PureEnum::from('four'); // throws ValueError -PureEnum::tryFrom('one'); // PureEnum::one +PureEnum::tryFrom('One'); // PureEnum::One PureEnum::tryFrom('four'); // null -PureEnum::fromName('one'); // PureEnum::one +PureEnum::fromName('One'); // PureEnum::One PureEnum::fromName('four'); // throws ValueError -PureEnum::tryFromName('one'); // PureEnum::one +PureEnum::tryFromName('One'); // PureEnum::One PureEnum::tryFromName('four'); // null -PureEnum::fromKey('name', 'one'); // CasesCollection +PureEnum::fromKey('name', 'One'); // CasesCollection[PureEnum::One] PureEnum::fromKey('value', 1); // throws ValueError -PureEnum::fromKey('color', 'red'); // CasesCollection -PureEnum::fromKey(fn(PureEnum $case) => $case->isOdd(), true); // CasesCollection -PureEnum::tryFromKey('name', 'one'); // CasesCollection +PureEnum::fromKey('color', 'red'); // CasesCollection[PureEnum::One] +PureEnum::fromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::fromKey(fn(PureEnum $case) => $case->isOdd()); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::tryFromKey('name', 'One'); // CasesCollection[PureEnum::One] PureEnum::tryFromKey('value', 1); // null -PureEnum::tryFromKey('color', 'red'); // CasesCollection -PureEnum::tryFromKey(fn(PureEnum $case) => $case->isOdd(), true); // CasesCollection +PureEnum::tryFromKey('color', 'red'); // CasesCollection[PureEnum::One] +PureEnum::tryFromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::tryFromKey(fn(PureEnum $case) => $case->isOdd()); // CasesCollection[PureEnum::One, PureEnum::Three] -BackedEnum::from(1); // BackedEnum::one +BackedEnum::from(1); // BackedEnum::One BackedEnum::from('1'); // throws ValueError -BackedEnum::tryFrom(1); // BackedEnum::one +BackedEnum::tryFrom(1); // BackedEnum::One BackedEnum::tryFrom('1'); // null -BackedEnum::fromName('one'); // BackedEnum::one +BackedEnum::fromName('One'); // BackedEnum::One BackedEnum::fromName('four'); // throws ValueError -BackedEnum::tryFromName('one'); // BackedEnum::one +BackedEnum::tryFromName('One'); // BackedEnum::One BackedEnum::tryFromName('four'); // null -BackedEnum::fromKey('name', 'one'); // CasesCollection -BackedEnum::fromKey('value', 1); // CasesCollection -BackedEnum::fromKey('color', 'red'); // CasesCollection -BackedEnum::fromKey(fn(BackedEnum $case) => $case->isOdd(), true); // CasesCollection -BackedEnum::tryFromKey('name', 'one'); // CasesCollection -BackedEnum::tryFromKey('value', 1); // CasesCollection -BackedEnum::tryFromKey('color', 'red'); // CasesCollection -BackedEnum::tryFromKey(fn(BackedEnum $case) => $case->isOdd(), true); // CasesCollection +BackedEnum::fromKey('name', 'One'); // CasesCollection[BackedEnum::One] +BackedEnum::fromKey('value', 1); // CasesCollection[BackedEnum::One] +BackedEnum::fromKey('color', 'red'); // CasesCollection[BackedEnum::One] +BackedEnum::fromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +BackedEnum::fromKey(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection[BackedEnum::One, BackedEnum::Three] +BackedEnum::tryFromKey('name', 'One'); // CasesCollection[BackedEnum::One] +BackedEnum::tryFromKey('value', 1); // CasesCollection[BackedEnum::One] +BackedEnum::tryFromKey('color', 'red'); // CasesCollection[BackedEnum::One] +BackedEnum::tryFromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +BackedEnum::tryFromKey(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection[BackedEnum::One, BackedEnum::Three] ``` -While pure enums try to hydrate cases from names, backed enums can hydrate from both names and values. Even keys can be used to hydrate cases, cases are then wrapped into a [`CasesCollection`](#cases-collection) to allow further processing. +Hydrating from keys may return multiple cases. To facilitate further processing, such cases are [collected into a `CasesCollection`](#cases-collection). -### Elaborating cases +### ๐ŸŽฒ Enum operations -There is a bunch of operations that can be performed on the cases of an enum. If the result of an operation is a plain list of cases, they get wrapped into a [`CasesCollection`](#cases-collection) for additional elaboration, otherwise the final result of the operation is returned: +A number of operations can be performed against an enum to affect all its cases: ```php -PureEnum::collect(); // CasesCollection +PureEnum::collect(); // CasesCollection[PureEnum::One, PureEnum::Two, PureEnum::Three] PureEnum::count(); // 3 -PureEnum::casesByName(); // ['one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three] -PureEnum::casesByValue(); // [] -PureEnum::casesBy('color'); // ['red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three] -PureEnum::groupBy('color'); // ['red' => [PureEnum::one], 'green' => [PureEnum::two], 'blue' => [PureEnum::three]] -PureEnum::names(); // ['one', 'two', 'three'] +PureEnum::first(); // PureEnum::One +PureEnum::first(fn(PureEnum $case, int $key) => ! $case->isOdd()); // PureEnum::Two +PureEnum::names(); // ['One', 'Two', 'Three'] PureEnum::values(); // [] -PureEnum::pluck('name'); // ['one', 'two', 'three'] +PureEnum::pluck('name'); // ['One', 'Two', 'Three'] PureEnum::pluck('color'); // ['red', 'green', 'blue'] PureEnum::pluck(fn(PureEnum $case) => $case->isOdd()); // [true, false, true] PureEnum::pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] -PureEnum::pluck(fn(PureEnum $case) => $case->isOdd(), fn(PureEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] -PureEnum::filter('isOdd'); // CasesCollection -PureEnum::filter(fn(PureEnum $case) => $case->isOdd()); // CasesCollection -PureEnum::only('two', 'three'); // CasesCollection -PureEnum::except('two', 'three'); // CasesCollection -PureEnum::onlyValues(2, 3); // CasesCollection<> -PureEnum::exceptValues(2, 3); // CasesCollection<> -PureEnum::sort(); // CasesCollection -PureEnum::sortDesc(); // CasesCollection -PureEnum::sortByValue(); // CasesCollection<> -PureEnum::sortByDescValue(); // CasesCollection<> -PureEnum::sortBy('color'); // CasesCollection -PureEnum::sortByDesc(fn(PureEnum $case) => $case->color()); // CasesCollection - -BackedEnum::collect(); // CasesCollection +PureEnum::pluck(fn(PureEnum $case) => $case->isOdd(), fn(PureEnum $case) => $case->name); // ['One' => true, 'Two' => false, 'Three' => true] +PureEnum::map(fn(PureEnum $case, int $key) => $case->name . $key); // ['One0', 'Two1', 'Three2'] +PureEnum::keyByName(); // CasesCollection['One' => PureEnum::One, 'Two' => PureEnum::Two, 'Three' => PureEnum::Three] +PureEnum::keyBy('color'); // CasesCollection['red' => PureEnum::One, 'green' => PureEnum::Two, 'blue' => PureEnum::Three] +PureEnum::keyByValue(); // CasesCollection[] +PureEnum::groupBy('color'); // CasesCollection['red' => CasesCollection[PureEnum::One], 'green' => CasesCollection[PureEnum::Two], 'blue' => CasesCollection[PureEnum::Three]] +PureEnum::filter('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::filter(fn(PureEnum $case) => $case->isOdd()); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::only('Two', 'Three'); // CasesCollection[PureEnum::Two, PureEnum::Three] +PureEnum::except('Two', 'Three'); // CasesCollection[PureEnum::One] +PureEnum::onlyValues(2, 3); // CasesCollection[] +PureEnum::exceptValues(2, 3); // CasesCollection[] +PureEnum::sort(); // CasesCollection[PureEnum::One, PureEnum::Three, PureEnum::Two] +PureEnum::sortBy('color'); // CasesCollection[PureEnum::Three, PureEnum::Two, PureEnum::One] +PureEnum::sortByValue(); // CasesCollection[] +PureEnum::sortDesc(); // CasesCollection[PureEnum::Two, PureEnum::Three, PureEnum::One] +PureEnum::sortByDesc(fn(PureEnum $case) => $case->color()); // CasesCollection[PureEnum::One, PureEnum::Two, PureEnum::Three] +PureEnum::sortByDescValue(); // CasesCollection[] + +BackedEnum::collect(); // CasesCollection[BackedEnum::One, BackedEnum::Two, BackedEnum::Three] BackedEnum::count(); // 3 -BackedEnum::casesByName(); // ['one' => BackedEnum::one, 'two' => BackedEnum::two, 'three' => BackedEnum::three] -BackedEnum::casesByValue(); // [1 => BackedEnum::one, 2 => BackedEnum::two, 3 => BackedEnum::three] -BackedEnum::casesBy('color'); // ['red' => BackedEnum::one, 'green' => BackedEnum::two, 'blue' => BackedEnum::three] -BackedEnum::groupBy('color'); // ['red' => [BackedEnum::one], 'green' => [BackedEnum::two], 'blue' => [BackedEnum::three]] -BackedEnum::names(); // ['one', 'two', 'three'] +BackedEnum::first(); // BackedEnum::One +BackedEnum::first(fn(BackedEnum $case, int $key) => ! $case->isOdd()); // BackedEnum::Two +BackedEnum::names(); // ['One', 'Two', 'Three'] BackedEnum::values(); // [1, 2, 3] BackedEnum::pluck('value'); // [1, 2, 3] BackedEnum::pluck('color'); // ['red', 'green', 'blue'] BackedEnum::pluck(fn(BackedEnum $case) => $case->isOdd()); // [true, false, true] BackedEnum::pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] -BackedEnum::pluck(fn(BackedEnum $case) => $case->isOdd(), fn(BackedEnum $case) => $case->name); // ['one' => true, -BackedEnum::filter('isOdd'); // CasesCollection -BackedEnum::filter(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection -BackedEnum::only('two', 'three'); // CasesCollection -BackedEnum::except('two', 'three'); // CasesCollection -BackedEnum::onlyValues(2, 3); // CasesCollection<> -BackedEnum::exceptValues(2, 3); // CasesCollection<>'two' => false, 'three' => true] -BackedEnum::sort(); // CasesCollection -BackedEnum::sortDesc(); // CasesCollection -BackedEnum::sortByValue(); // CasesCollection -BackedEnum::sortByDescValue(); // CasesCollection -BackedEnum::sortBy('color'); // CasesCollection -BackedEnum::sortByDesc(fn(BackedEnum $case) => $case->color()); // CasesCollection +BackedEnum::pluck(fn(BackedEnum $case) => $case->isOdd(), fn(BackedEnum $case) => $case->name); // ['One' => true, 'Two' => false, 'Three' => true] +BackedEnum::map(fn(BackedEnum $case, int $key) => $case->name . $key); // ['One0', 'Two1', 'Three2'] +BackedEnum::keyByName(); // CasesCollection['One' => BackedEnum::One, 'Two' => BackedEnum::Two, 'Three' => BackedEnum::Three] +BackedEnum::keyBy('color'); // CasesCollection['red' => BackedEnum::One, 'green' => BackedEnum::Two, 'blue' => BackedEnum::Three] +BackedEnum::keyByValue(); // CasesCollection[1 => BackedEnum::One, 2 => BackedEnum::Two, 3 => BackedEnum::Three] +BackedEnum::groupBy('color'); // CasesCollection['red' => CasesCollection[BackedEnum::One], 'green' => CasesCollection[BackedEnum::Two], 'blue' => CasesCollection[BackedEnum::Three]] +BackedEnum::filter('isOdd'); // CasesCollection[BackedEnum::One, BackedEnum::Three] +BackedEnum::filter(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection[BackedEnum::One, BackedEnum::Three] +BackedEnum::only('Two', 'Three'); // CasesCollection[BackedEnum::Two, BackedEnum::Three] +BackedEnum::except('Two', 'Three'); // CasesCollection[BackedEnum::One] +BackedEnum::onlyValues(2, 3); // CasesCollection[] +BackedEnum::exceptValues(2, 3); // CasesCollection['Two' => false, 'Three' => true] +BackedEnum::sort(); // CasesCollection[BackedEnum::One, BackedEnum::Three, BackedEnum::Two] +BackedEnum::sortBy('color'); // CasesCollection[BackedEnum::Three, BackedEnum::Two, BackedEnum::One] +BackedEnum::sortByValue(); // CasesCollection[BackedEnum::One, BackedEnum::Two, BackedEnum::Three] +BackedEnum::sortDesc(); // CasesCollection[BackedEnum::Two, BackedEnum::Three, BackedEnum::One] +BackedEnum::sortByDescValue(); // CasesCollection[BackedEnum::Three, BackedEnum::Two, BackedEnum::One] +BackedEnum::sortByDesc(fn(BackedEnum $case) => $case->color()); // CasesCollection[BackedEnum::One, BackedEnum::Two, BackedEnum::Three] ``` -### Cases collection +### ๐Ÿงบ Cases collection -When a plain list of cases is returned by one of the [cases operations](#elaborating-cases), it gets wrapped into a `CasesCollection` which provides a fluent API to perform further operations on the set of cases: +When an [enum operation](#-enum-operations) can return multiple cases, they are collected into a `CasesCollection` which provides a fluent API to perform further operations on the set of cases: ```php -PureEnum::filter('isOdd')->sortBy('color')->pluck('color', 'name'); // ['three' => 'blue', 'one' => 'red'] +PureEnum::filter('isOdd')->sortBy('color')->pluck('color', 'name'); // ['Three' => 'blue', 'One' => 'red'] ``` -Cases can be collected by calling `collect()` or any other [cases operation](#elaborating-cases) returning a `CasesCollection`: +Cases can be collected by calling `collect()` or any other [enum operation](#-enum-operations) returning a `CasesCollection`: ```php -PureEnum::collect(); // CasesCollection +PureEnum::collect(); // CasesCollection[PureEnum::One, PureEnum::Two, PureEnum::Three] -BackedEnum::only('one', 'two'); // CasesCollection +BackedEnum::only('One', 'Two'); // CasesCollection[BackedEnum::One, BackedEnum::Two] ``` -We can iterate cases collections within any loop: +We can iterate a cases collection within any loop: ```php foreach (PureEnum::collect() as $case) { @@ -303,74 +281,32 @@ foreach (PureEnum::collect() as $case) { } ``` -Obtaining the underlying plain list of cases is easy: +All the [enum operations listed above](#-enum-operations) are also available when dealing with a collection of cases. -```php -PureEnum::collect()->all(); // [PureEnum::one, PureEnum::two, PureEnum::three] -``` -Sometimes we may need to extract only the first case of the collection: +### ๐Ÿช„ Magic + +Enums can implement magic methods to be invoked or to handle calls to inaccessible methods. By default when calling an inaccessible static method, the value of the case matching the missing method is returned: ```php -PureEnum::filter(fn(PureEnum $case) => !$case->isOdd())->first(); // PureEnum::two +PureEnum::One(); // 'One' +PureEnum::isBacked(); // false + +BackedEnum::isPure(); // false +BackedEnum::isBacked(); // true ``` -For reference, here are all the operations available in `CasesCollection`: + +### ๐Ÿคณ Self-awareness + +These methods determine whether an enum is pure or backed: ```php -PureEnum::collect()->all(); // [PureEnum::one, PureEnum::two, PureEnum::three] -PureEnum::collect()->count(); // 3 -PureEnum::collect()->first(); // PureEnum::one -PureEnum::collect()->keyByName(); // CasesCollection<'one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three> -PureEnum::collect()->keyByValue(); // CasesCollection<> -PureEnum::collect()->keyBy('color'); // CasesCollection<'red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three> -PureEnum::collect()->groupBy('color'); // CasesCollection<'red' => CasesCollection, 'green' => CasesCollection, 'blue' => CasesCollection> -PureEnum::collect()->names(); // ['one', 'two', 'three'] -PureEnum::collect()->values(); // [] -PureEnum::collect()->pluck(); // ['one', 'two', 'three'] -PureEnum::collect()->pluck('color'); // ['red', 'green', 'blue'] -PureEnum::collect()->pluck(fn(PureEnum $case) => $case->isOdd()); // [true, false, true] -PureEnum::collect()->pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] -PureEnum::collect()->pluck(fn(PureEnum $case) => $case->isOdd(), fn(PureEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] -PureEnum::collect()->filter('isOdd'); // CasesCollection -PureEnum::collect()->filter(fn(PureEnum $case) => $case->isOdd()); // CasesCollection -PureEnum::collect()->only('two', 'three'); // CasesCollection -PureEnum::collect()->except('two', 'three'); // CasesCollection -PureEnum::collect()->onlyValues(2, 3); // CasesCollection<> -PureEnum::collect()->exceptValues(2, 3); // CasesCollection<> -PureEnum::collect()->sort(); // CasesCollection -PureEnum::collect()->sortDesc(); // CasesCollection -PureEnum::collect()->sortByValue(); // CasesCollection<> -PureEnum::collect()->sortByDescValue(); // CasesCollection<> -PureEnum::collect()->sortBy('color'); // CasesCollection -PureEnum::collect()->sortByDesc(fn(PureEnum $case) => $case->color()); // CasesCollection - -BackedEnum::collect()->all(); // [BackedEnum::one, BackedEnum::two, BackedEnum::three] -BackedEnum::collect()->count(); // 3 -BackedEnum::collect()->first(); // BackedEnum::one -BackedEnum::collect()->keyByName(); // CasesCollection<'one' => BackedEnum::one, 'two' => BackedEnum::two, 'three' => BackedEnum::three> -BackedEnum::collect()->keyByValue(); // CasesCollection<1 => BackedEnum::one, 2 => BackedEnum::two, 3 => BackedEnum::three> -BackedEnum::collect()->keyBy('color'); // CasesCollection<'red' => BackedEnum::one, 'green' => BackedEnum::two, 'blue' => BackedEnum::three> -BackedEnum::collect()->groupBy('color'); // CasesCollection<'red' => CasesCollection, 'green' => CasesCollection, 'blue' => CasesCollection> -BackedEnum::collect()->names(); // ['one', 'two', 'three'] -BackedEnum::collect()->values(); // [1, 2, 3] -BackedEnum::collect()->pluck(); // [1, 2, 3] -BackedEnum::collect()->pluck('color'); // ['red', 'green', 'blue'] -BackedEnum::collect()->pluck(fn(BackedEnum $case) => $case->isOdd()); // [true, false, true] -BackedEnum::collect()->pluck('color', 'shape'); // ['triangle' => 'red', 'square' => 'green', 'circle' => 'blue'] -BackedEnum::collect()->pluck(fn(BackedEnum $case) => $case->isOdd(), fn(BackedEnum $case) => $case->name); // ['one' => true, 'two' => false, 'three' => true] -BackedEnum::collect()->filter('isOdd'); // CasesCollection -BackedEnum::collect()->filter(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection -BackedEnum::collect()->only('two', 'three'); // CasesCollection -BackedEnum::collect()->except('two', 'three'); // CasesCollection -BackedEnum::collect()->onlyValues(2, 3); // CasesCollection -BackedEnum::collect()->exceptValues(2, 3); // CasesCollection -BackedEnum::collect()->sort(); // CasesCollection -BackedEnum::collect()->sortDesc(); // CasesCollection -BackedEnum::collect()->sortByValue(); // CasesCollection -BackedEnum::collect()->sortByDescValue(); // CasesCollection -BackedEnum::collect()->sortBy('color'); // CasesCollection -BackedEnum::collect()->sortByDesc(fn(BackedEnum $case) => $case->color()); // CasesCollection +PureEnum::isPure(); // true +PureEnum::isBacked(); // false + +BackedEnum::isPure(); // false +BackedEnum::isBacked(); // true ``` ## ๐Ÿ“† Change log From 2a39ba8dd851f0d0271e1edd5f3c01c36db172cd Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Fri, 4 Oct 2024 17:47:03 -0300 Subject: [PATCH 40/49] Update readme --- README.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b5b6948..23c1c46 100644 --- a/README.md +++ b/README.md @@ -135,13 +135,6 @@ enum BackedEnum: int } ``` -We can see the keys belonging to an enum by calling the `keys()` method: - -```php -// ['name', 'value', 'color', 'isOdd'] -$keys = BackedEnum::keys(); -``` - Keys provide extra information for an enum and can also be leveraged for the [hydration](#-hydration), [elaboration](#-enum-operations) and [collection](#-cases-collection) of cases. @@ -286,27 +279,112 @@ All the [enum operations listed above](#-enum-operations) are also available whe ### ๐Ÿช„ Magic -Enums can implement magic methods to be invoked or to handle calls to inaccessible methods. By default when calling an inaccessible static method, the value of the case matching the missing method is returned: +Enums can implement magic methods to be invoked or to handle calls to inaccessible methods. By default when calling an inaccessible static method, the name or value of the case matching the missing method is returned: ```php PureEnum::One(); // 'One' -PureEnum::isBacked(); // false -BackedEnum::isPure(); // false -BackedEnum::isBacked(); // true +BackedEnum::One(); // 1 +``` + +To improve the autocompletion of our IDE, we can add some method annotations to our enums: + +```php +/** + * @method static int One() + * @method static int Two() + * @method static int Three() + */ +enum BackedEnum: int +{ + use Enumerates; + + case One = 1; + case Two = 2; + case Three = 3; +} +``` + +By default, we can also obtain the name or value of a case by simply invoking it: + +```php +$case = PureEnum::One; +$case(); // 'One' + +$case = BackedEnum::One; +$case(); // 1 +``` + +When calling an inaccessible method of a case, by default the value of the key matching the missing method is returned: + +```php +PureEnum::One->color(); // 'red' + +BackedEnum::One->color(); // 'red' +``` + +To improve the autocompletion of our IDE, we can add some method annotations to our enums: + +```php +/** + * @method string color() + */ +enum BackedEnum: int +{ + use Enumerates; + + #[Meta(color: 'red')] + case One = 1; + + #[Meta(color: 'green')] + case Two = 2; + + #[Meta(color: 'blue')] + case Three = 3; +} +``` + +Depending on our needs, we can customize the default magic behavior of all enums in our application and run our own custom logic when invoking a case or calling inaccessible methods: + +```php +use Cerbero\Enum\Enums; + +// define the logic to run when calling an inaccessible method of an enum +Enums::onStaticCall(function(string $enum, string $name, array $arguments) { + // $enum is the fully qualified name of the enum that called the inaccessible method + // $name is the inaccessible method name + // $arguments are the parameters passed to the inaccessible method +}); + +// define the logic to run when calling an inaccessible method of a case +Enums::onCall(function(object $case, string $name, array $arguments) { + // $case is the instance of the case that called the inaccessible method + // $name is the inaccessible method name + // $arguments are the parameters passed to the inaccessible method +}); + +// define the logic to run when invoking a case +Enums::onInvoke(function(object $case, mixed ...$arguments) { + // $case is the instance of the case that is being invoked + // $arguments are the parameters passed when invoking the case +}); ``` ### ๐Ÿคณ Self-awareness -These methods determine whether an enum is pure or backed: +Finally, the following methods can be useful for inspecting enums or auto-generating code: ```php PureEnum::isPure(); // true PureEnum::isBacked(); // false +PureEnum::keys(); // ['color', 'shape', 'isOdd'] +PureEnum::One->value(); // 'One' BackedEnum::isPure(); // false BackedEnum::isBacked(); // true +BackedEnum::keys(); // ['color', 'shape', 'isOdd'] +BackedEnum::One->value(); // 1 ``` ## ๐Ÿ“† Change log From 0c243a27915dab8948c312432b2370f48c329daa Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Fri, 4 Oct 2024 17:48:37 -0300 Subject: [PATCH 41/49] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23c1c46..15b7982 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ composer require cerbero/enum ## ๐Ÿ”ฎ Usage -* [โš–๏ธ Comparison](#-comparison) +* [โš–๏ธ Comparison](#%EF%B8%8F-comparison) * [๐Ÿ”‘ Keys](#-keys) * [๐Ÿšฐ Hydration](#-hydration) * [๐ŸŽฒ Enum operations](#-enum-operations) From c871b1fd23ab9dcdff562b77522546e43d092ffd Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Fri, 4 Oct 2024 17:50:17 -0300 Subject: [PATCH 42/49] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15b7982..a578a05 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ BackedEnum::tryFromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Thr BackedEnum::tryFromKey(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection[BackedEnum::One, BackedEnum::Three] ``` -Hydrating from keys may return multiple cases. To facilitate further processing, such cases are [collected into a `CasesCollection`](#cases-collection). +Hydrating from keys may return multiple cases. To facilitate further processing, such cases are [collected into a `CasesCollection`](#-cases-collection). ### ๐ŸŽฒ Enum operations From 8ab79e7bb02b83a062166d166284404ee500204e Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 5 Oct 2024 13:04:45 -0300 Subject: [PATCH 43/49] Introduce meta --- src/Attributes/Meta.php | 62 +++++++++++++++++++++++ src/CasesCollection.php | 14 +++--- src/Concerns/Hydrates.php | 20 ++++---- src/Concerns/SelfAware.php | 89 +++++++++++++++++++++++++++------- src/Enums.php | 6 +-- tests/BackedEnum.php | 41 +++------------- tests/BackedEnumTest.php | 43 ++++++++-------- tests/InvalidMetaAttribute.php | 19 ++++++++ tests/PureEnum.php | 41 +++------------- tests/PureEnumTest.php | 47 +++++++++--------- 10 files changed, 230 insertions(+), 152 deletions(-) create mode 100644 src/Attributes/Meta.php create mode 100644 tests/InvalidMetaAttribute.php diff --git a/src/Attributes/Meta.php b/src/Attributes/Meta.php new file mode 100644 index 0000000..c9443a7 --- /dev/null +++ b/src/Attributes/Meta.php @@ -0,0 +1,62 @@ + + */ + protected array $all; + + /** + * Instantiate the class. + */ + public function __construct(mixed ...$meta) + { + foreach ($meta as $key => $value) { + if (! is_string($key)) { + throw new InvalidArgumentException('The name of meta must be a string'); + } + + $this->all[$key] = $value; + } + } + + /** + * Retrieve the meta names. + * + * @return string[] + */ + public function names(): array + { + return array_keys($this->all); + } + + /** + * Determine whether the given meta exists. + */ + public function has(string $meta): bool + { + return array_key_exists($meta, $this->all); + } + + /** + * Retrieve the value for the given meta. + */ + public function get(string $meta): mixed + { + return $this->all[$meta] ?? null; + } +} diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 3b1b991..b8a2e22 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -130,9 +130,9 @@ public function pluck(callable|string $value, callable|string $key = null): arra foreach ($this->cases as $case) { if ($key === null) { - $result[] = $case->resolveKey($value); + $result[] = $case->resolveCaseItem($value); } else { - $result[$case->resolveKey($key)] = $case->resolveKey($value); + $result[$case->resolveCaseItem($key)] = $case->resolveCaseItem($value); } } @@ -173,7 +173,7 @@ public function keyBy(callable|string $key): static $keyed = []; foreach ($this->cases as $case) { - $keyed[$case->resolveKey($key)] = $case; + $keyed[$case->resolveCaseItem($key)] = $case; } return new static($keyed); @@ -197,7 +197,7 @@ public function groupBy(callable|string $key): static $grouped = []; foreach ($this->cases as $case) { - $grouped[$case->resolveKey($key)][] = $case; + $grouped[$case->resolveCaseItem($key)][] = $case; } foreach ($grouped as $key => $cases) { @@ -215,7 +215,7 @@ public function groupBy(callable|string $key): static public function filter(callable|string $filter): static { /** @phpstan-ignore method.nonObject */ - $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->resolveKey($filter) === true; + $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->resolveCaseItem($filter) === true; return new static(array_filter($this->cases, $callback)); } @@ -269,7 +269,7 @@ public function sortBy(callable|string $key): static { $cases = $this->cases; - uasort($cases, fn(mixed $a, mixed $b) => $a->resolveKey($key) <=> $b->resolveKey($key)); + uasort($cases, fn(mixed $a, mixed $b) => $a->resolveCaseItem($key) <=> $b->resolveCaseItem($key)); return new static($cases); } @@ -299,7 +299,7 @@ public function sortByDesc(callable|string $key): static { $cases = $this->cases; - uasort($cases, fn(mixed $a, mixed $b) => $b->resolveKey($key) <=> $a->resolveKey($key)); + uasort($cases, fn(mixed $a, mixed $b) => $b->resolveCaseItem($key) <=> $a->resolveCaseItem($key)); return new static($cases); } diff --git a/src/Concerns/Hydrates.php b/src/Concerns/Hydrates.php index 4aedfc1..5b6afef 100644 --- a/src/Concerns/Hydrates.php +++ b/src/Concerns/Hydrates.php @@ -59,35 +59,33 @@ public static function tryFrom(string $name): ?static } /** - * Retrieve all the cases hydrated from the given key or fail. + * Retrieve all the cases hydrated from the given meta or fail. * - * @param (callable(self): mixed)|string $key * @return CasesCollection * @throws ValueError */ - public static function fromKey(callable|string $key, mixed $value = true): CasesCollection + public static function fromMeta(string $meta, mixed $value = true): CasesCollection { - if ($cases = self::tryFromKey($key, $value)) { + if ($cases = self::tryFromMeta($meta, $value)) { return $cases; } - $target = is_callable($key) ? 'given callable key' : "key \"{$key}\""; - - throw new ValueError(sprintf('Invalid value for the %s for enum "%s"', $target, self::class)); + throw new ValueError(sprintf('Invalid value for the meta "%s" for enum "%s"', $meta, self::class)); } /** - * Retrieve all the cases hydrated from the given key or NULL. + * Retrieve all the cases hydrated from the given meta or NULL. * - * @param (callable(self): mixed)|string $key * @return ?CasesCollection */ - public static function tryFromKey(callable|string $key, mixed $value = true): ?CasesCollection + public static function tryFromMeta(string $meta, mixed $value = true): ?CasesCollection { $cases = []; foreach (self::cases() as $case) { - if ($case->resolveKey($key) === $value) { + $metaValue = $case->resolveMeta($meta); + + if ((is_callable($value) && $value($metaValue) === true) || $metaValue === $value) { $cases[] = $case; } } diff --git a/src/Concerns/SelfAware.php b/src/Concerns/SelfAware.php index 3573cd8..bbf3989 100644 --- a/src/Concerns/SelfAware.php +++ b/src/Concerns/SelfAware.php @@ -3,9 +3,11 @@ namespace Cerbero\Enum\Concerns; use BackedEnum; -use ReflectionClass; +use Cerbero\Enum\Attributes\Meta; +use ReflectionAttribute; +use ReflectionEnum; +use ReflectionEnumUnitCase; use ReflectionMethod; -use Throwable; use ValueError; /** @@ -30,42 +32,93 @@ public static function isBacked(): bool } /** - * Retrieve all the keys of the enum. + * Retrieve all the meta names of the enum. * * @return string[] */ - public static function keys(): array + public static function metaNames(): array { - $enum = new ReflectionClass(self::class); - $keys = self::isPure() ? ['name'] : ['name', 'value']; + $meta = []; + $enum = new ReflectionEnum(self::class); + + foreach ($enum->getAttributes(Meta::class) as $attribute) { + array_push($meta, ...$attribute->newInstance()->names()); + } + + foreach ($enum->getCases() as $case) { + foreach ($case->getAttributes(Meta::class) as $attribute) { + array_push($meta, ...$attribute->newInstance()->names()); + } + } foreach ($enum->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { if (! $method->isStatic() && $method->getFileName() == $enum->getFileName()) { - $keys[] = $method->getShortName(); + $meta[] = $method->getShortName(); } } - return $keys; + return array_values(array_unique($meta)); } /** - * Retrieve the given key of this case. + * Retrieve the given item of this case. * - * @template TGetValue + * @template TItemValue * - * @param (callable(self): TGetValue)|string $key - * @return TGetValue + * @param (callable(self): TItemValue)|string $item + * @return TItemValue * @throws ValueError */ - public function resolveKey(callable|string $key): mixed + public function resolveCaseItem(callable|string $item): mixed { - try { - return is_callable($key) ? $key($this) : ($this->$key ?? $this->$key()); - } catch (Throwable) { - $target = is_callable($key) ? 'The given callable' : "\"{$key}\""; + return match (true) { + is_callable($item) => $item($this), + property_exists($this, $item) => $this->$item, + default => $this->resolveMeta($item), + }; + } - throw new ValueError(sprintf('%s is not a valid key for enum "%s"', $target, self::class)); + /** + * Retrieve the given meta of this case. + * + * @throws ValueError + */ + public function resolveMeta(string $meta): mixed + { + $enum = new ReflectionEnum($this); + $enumFileName = $enum->getFileName(); + + foreach ($enum->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if (! $method->isStatic() && $method->getFileName() == $enumFileName && $method->getShortName() == $meta) { + return $this->$meta(); + } } + + return $this->resolveMetaAttribute($meta); + } + + /** + * Retrieve the given meta from the attributes. + * + * @throws ValueError + */ + public function resolveMetaAttribute(string $meta): mixed + { + $case = new ReflectionEnumUnitCase($this, $this->name); + + foreach ($case->getAttributes(Meta::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + if (($metadata = $attribute->newInstance())->has($meta)) { + return $metadata->get($meta); + } + } + + foreach ($case->getEnum()->getAttributes(Meta::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + if (($metadata = $attribute->newInstance())->has($meta)) { + return $metadata->get($meta); + } + } + + throw new ValueError(sprintf('"%s" is not a valid meta for enum "%s"', $meta, self::class)); } /** diff --git a/src/Enums.php b/src/Enums.php index 7d0e202..1ddc0aa 100644 --- a/src/Enums.php +++ b/src/Enums.php @@ -5,7 +5,6 @@ namespace Cerbero\Enum; use Closure; -use Error; /** * The global behavior for all enums. @@ -83,9 +82,8 @@ public static function handleStaticCall(string $enum, string $name, array $argum */ public static function handleCall(object $case, string $name, array $arguments): mixed { - return static::$onCall - ? (static::$onCall)($case, $name, $arguments) /** @phpstan-ignore-next-line property.notFound */ - : throw new Error(sprintf('Call to undefined method %s::%s->%s()', $case::class, $case->name, $name)); + /** @phpstan-ignore method.notFound */ + return static::$onCall ? (static::$onCall)($case, $name, $arguments) : $case->resolveMetaAttribute($name); } /** diff --git a/tests/BackedEnum.php b/tests/BackedEnum.php index df216dc..b020853 100644 --- a/tests/BackedEnum.php +++ b/tests/BackedEnum.php @@ -2,47 +2,24 @@ namespace Cerbero\Enum; +use Cerbero\Enum\Attributes\Meta; use Cerbero\Enum\Concerns\Enumerates; /** * The backed enum to test. - * */ +#[Meta(color: 'green', shape: 'square')] enum BackedEnum: int { use Enumerates; + #[Meta(color: 'red', shape: 'triangle')] case one = 1; - case two = 2; - case three = 3; - /** - * Retrieve the color of the case - * - * @return string - */ - public function color(): string - { - return match ($this) { - self::one => 'red', - self::two => 'green', - self::three => 'blue', - }; - } + case two = 2; - /** - * Retrieve the shape of the case - * - * @return string - */ - public function shape(): string - { - return match ($this) { - self::one => 'triangle', - self::two => 'square', - self::three => 'circle', - }; - } + #[Meta(color: 'blue', shape: 'circle')] + case three = 3; /** * Retrieve whether the case is odd @@ -51,10 +28,6 @@ public function shape(): string */ public function isOdd(): bool { - return match ($this) { - self::one => true, - self::two => false, - self::three => true, - }; + return $this->value % 2 != 0; } } diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index bee3690..ad5ee11 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -100,7 +100,7 @@ ->all() ->toBe([BackedEnum::one, BackedEnum::two]); -it('retrieves a collection with cases filtered by a key', function () { +it('retrieves a collection with cases filtered by a meta', function () { expect(BackedEnum::filter('isOdd')) ->toBeInstanceOf(CasesCollection::class) ->all() @@ -330,37 +330,36 @@ ['four', null], ]); -it('retrieves the cases hydrated from a key') - ->expect(fn(string $key, mixed $value, array $cases) => BackedEnum::fromKey($key, $value)->all() === $cases) +it('retrieves the cases hydrated from a meta') + ->expect(fn(string $meta, mixed $value, array $cases) => BackedEnum::fromMeta($meta, $value)->all() === $cases) ->toBeTrue() ->with([ ['color', 'red', [BackedEnum::one]], - ['name', 'three', [BackedEnum::three]], + ['shape', 'circle', [BackedEnum::three]], ['isOdd', true, [BackedEnum::one, BackedEnum::three]], ]); -it('retrieves the cases hydrated from a key using a closure') - ->expect(BackedEnum::fromKey(fn(BackedEnum $case) => $case->shape(), 'square')) +it('retrieves the cases hydrated from a meta using a closure') + ->expect(BackedEnum::fromMeta('shape', fn(string $shape) => $shape == 'square')) ->toBeInstanceOf(CasesCollection::class) ->all() ->toBe([BackedEnum::two]); -it('throws a value error when hydrating cases with an invalid key', fn() => BackedEnum::fromKey('color', 'orange')) - ->throws(ValueError::class, 'Invalid value for the key "color" for enum "Cerbero\Enum\BackedEnum"'); +it('throws a value error when hydrating cases with an invalid meta', fn() => BackedEnum::fromMeta('color', 'orange')) + ->throws(ValueError::class, 'Invalid value for the meta "color" for enum "Cerbero\Enum\BackedEnum"'); -it('retrieves the case hydrated from a key or returns null') - ->expect(fn(string $key, mixed $value, ?array $cases) => BackedEnum::tryFromKey($key, $value)?->all() === $cases) +it('retrieves the case hydrated from a meta or returns null') + ->expect(fn(string $meta, mixed $value, ?array $cases) => BackedEnum::tryFromMeta($meta, $value)?->all() === $cases) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ ['color', 'red', [BackedEnum::one]], - ['name', 'three', [BackedEnum::three]], ['isOdd', true, [BackedEnum::one, BackedEnum::three]], ['shape', 'rectangle', null], ]); -it('attempts to retrieve the case hydrated from a key using a closure') - ->expect(BackedEnum::tryFromKey(fn(BackedEnum $case) => $case->shape(), 'square')) +it('attempts to retrieve the case hydrated from a meta using a closure') + ->expect(BackedEnum::tryFromMeta('shape', fn(string $shape) => $shape == 'square')) ->toBeInstanceOf(CasesCollection::class) ->all() ->toBe([BackedEnum::two]); @@ -387,7 +386,7 @@ }); it('handles the call to an inaccessible case method', fn() => BackedEnum::one->unknownMethod()) - ->throws(Error::class, 'Call to undefined method Cerbero\Enum\BackedEnum::one->unknownMethod()'); + ->throws(Error::class, '"unknownMethod" is not a valid meta for enum "Cerbero\Enum\BackedEnum"'); it('runs custom logic when calling an inaccessible case method', function() { Enums::onCall(function(object $case, string $name, array $arguments) { @@ -420,12 +419,12 @@ (fn() => self::$onInvoke = null)->bindTo(null, Enums::class)(); }); -it('retrieves the keys of an enum', function() { - expect(BackedEnum::keys())->toBe(['name', 'value', 'color', 'shape', 'isOdd']); +it('retrieves the meta names of an enum', function() { + expect(BackedEnum::metaNames())->toBe(['color', 'shape', 'isOdd']); }); -it('retrieves the key of a case') - ->expect(fn(string $key, mixed $value) => BackedEnum::one->resolveKey($key) === $value) +it('retrieves a case item') + ->expect(fn(string $item, mixed $value) => BackedEnum::one->resolveCaseItem($item) === $value) ->toBeTrue() ->with([ ['name', 'one'], @@ -434,12 +433,12 @@ ['shape', 'triangle'], ]); -it('retrieves the key of a case using a closure') - ->expect(BackedEnum::one->resolveKey(fn(BackedEnum $case) => $case->color())) +it('retrieves the item of a case using a closure') + ->expect(BackedEnum::one->resolveCaseItem(fn(BackedEnum $case) => $case->color())) ->toBe('red'); -it('throws a value error when attempting to retrieve an invalid key', fn() => BackedEnum::one->resolveKey('invalid')) - ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\BackedEnum"'); +it('throws a value error when attempting to retrieve an invalid item', fn() => BackedEnum::one->resolveCaseItem('invalid')) + ->throws(ValueError::class, '"invalid" is not a valid meta for enum "Cerbero\Enum\BackedEnum"'); it('retrieves the value of a backed case or the name of a pure case', function() { expect(BackedEnum::one->value())->toBe(1); diff --git a/tests/InvalidMetaAttribute.php b/tests/InvalidMetaAttribute.php new file mode 100644 index 0000000..4889037 --- /dev/null +++ b/tests/InvalidMetaAttribute.php @@ -0,0 +1,19 @@ + 'red', - self::two => 'green', - self::three => 'blue', - }; - } + case two; - /** - * Retrieve the shape of the case - * - * @return string - */ - public function shape(): string - { - return match ($this) { - self::one => 'triangle', - self::two => 'square', - self::three => 'circle', - }; - } + #[Meta(color: 'blue', shape: 'circle')] + case three; /** * Retrieve whether the case is odd @@ -51,10 +28,6 @@ public function shape(): string */ public function isOdd(): bool { - return match ($this) { - self::one => true, - self::two => false, - self::three => true, - }; + return $this->name != 'two'; } } diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index ffdfecc..a9a8bb8 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -2,6 +2,7 @@ use Cerbero\Enum\CasesCollection; use Cerbero\Enum\Enums; +use Cerbero\Enum\InvalidMetaAttribute; use Cerbero\Enum\PureEnum; use Pest\Expectation; @@ -103,7 +104,7 @@ ->toBe([PureEnum::one, PureEnum::two]); }); -it('retrieves a collection with cases filtered by a key', function () { +it('retrieves a collection with cases filtered by a meta', function () { expect(PureEnum::filter('isOdd')) ->toBeInstanceOf(CasesCollection::class) ->all() @@ -346,37 +347,36 @@ ['four', null], ]); -it('retrieves the cases hydrated from a key') - ->expect(fn(string $key, mixed $value, array $cases) => PureEnum::fromKey($key, $value)->all() === $cases) +it('retrieves the cases hydrated from a meta') + ->expect(fn(string $meta, mixed $value, array $cases) => PureEnum::fromMeta($meta, $value)->all() === $cases) ->toBeTrue() ->with([ ['color', 'red', [PureEnum::one]], - ['name', 'three', [PureEnum::three]], + ['shape', 'circle', [PureEnum::three]], ['isOdd', true, [PureEnum::one, PureEnum::three]], ]); -it('retrieves the cases hydrated from a key using a closure') - ->expect(PureEnum::fromKey(fn(PureEnum $case) => $case->shape(), 'square')) +it('retrieves the cases hydrated from a meta using a closure') + ->expect(PureEnum::fromMeta('shape', fn(string $meta) => $meta == 'square')) ->toBeInstanceOf(CasesCollection::class) ->all() ->toBe([PureEnum::two]); -it('throws a value error when hydrating cases with an invalid key', fn() => PureEnum::fromKey('color', 'orange')) - ->throws(ValueError::class, 'Invalid value for the key "color" for enum "Cerbero\Enum\PureEnum"'); +it('throws a value error when hydrating cases with an invalid meta', fn() => PureEnum::fromMeta('color', 'orange')) + ->throws(ValueError::class, 'Invalid value for the meta "color" for enum "Cerbero\Enum\PureEnum"'); -it('retrieves the case hydrated from a key or returns null') - ->expect(fn(string $key, mixed $value, ?array $cases) => PureEnum::tryFromKey($key, $value)?->all() === $cases) +it('retrieves the case hydrated from a meta or returns null') + ->expect(fn(string $meta, mixed $value, ?array $cases) => PureEnum::tryFromMeta($meta, $value)?->all() === $cases) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ ['color', 'red', [PureEnum::one]], - ['name', 'three', [PureEnum::three]], ['isOdd', true, [PureEnum::one, PureEnum::three]], ['shape', 'rectangle', null], ]); -it('attempts to retrieve the case hydrated from a key using a closure') - ->expect(PureEnum::tryFromKey(fn(PureEnum $case) => $case->shape(), 'square')) +it('attempts to retrieve the case hydrated from a meta using a closure') + ->expect(PureEnum::fromMeta('shape', fn(string $meta) => $meta == 'square')) ->toBeInstanceOf(CasesCollection::class) ->all() ->toBe([PureEnum::two]); @@ -403,7 +403,7 @@ }); it('handles the call to an inaccessible case method', fn() => PureEnum::one->unknownMethod()) - ->throws(Error::class, 'Call to undefined method Cerbero\Enum\PureEnum::one->unknownMethod()'); + ->throws(Error::class, '"unknownMethod" is not a valid meta for enum "Cerbero\Enum\PureEnum"'); it('runs custom logic when calling an inaccessible case method', function() { Enums::onCall(function(object $case, string $name, array $arguments) { @@ -436,12 +436,12 @@ (fn() => self::$onInvoke = null)->bindTo(null, Enums::class)(); }); -it('retrieves the keys of an enum', function() { - expect(PureEnum::keys())->toBe(['name', 'color', 'shape', 'isOdd']); +it('retrieves the meta names of an enum', function() { + expect(PureEnum::metaNames())->toBe(['color', 'shape', 'isOdd']); }); -it('retrieves the key of a case') - ->expect(fn(string $key, mixed $value) => PureEnum::one->resolveKey($key) === $value) +it('retrieves the item of a case') + ->expect(fn(string $item, mixed $value) => PureEnum::one->resolveCaseItem($item) === $value) ->toBeTrue() ->with([ ['name', 'one'], @@ -449,13 +449,16 @@ ['shape', 'triangle'], ]); -it('retrieves the key of a case using a closure') - ->expect(PureEnum::one->resolveKey(fn(PureEnum $case) => $case->color())) +it('retrieves the item of a case using a closure') + ->expect(PureEnum::one->resolveCaseItem(fn(PureEnum $case) => $case->color())) ->toBe('red'); -it('throws a value error when attempting to retrieve an invalid key', fn() => PureEnum::one->resolveKey('invalid')) - ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); +it('throws a value error when attempting to retrieve an invalid item', fn() => PureEnum::one->resolveCaseItem('invalid')) + ->throws(ValueError::class, '"invalid" is not a valid meta for enum "Cerbero\Enum\PureEnum"'); it('retrieves the value of a backed case or the name of a pure case', function() { expect(PureEnum::one->value())->toBe('one'); }); + +it('fails if a meta attribute does not have a name', fn() => InvalidMetaAttribute::metaNames()) + ->throws(InvalidArgumentException::class, 'The name of meta must be a string'); From 8d24cc41cac64507e538702d67dab6ab5cb02af3 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 5 Oct 2024 13:19:30 -0300 Subject: [PATCH 44/49] Rename method to resolveItem() --- src/CasesCollection.php | 14 +++++++------- src/Concerns/SelfAware.php | 2 +- tests/BackedEnumTest.php | 6 +++--- tests/PureEnumTest.php | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index b8a2e22..c50fbea 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -130,9 +130,9 @@ public function pluck(callable|string $value, callable|string $key = null): arra foreach ($this->cases as $case) { if ($key === null) { - $result[] = $case->resolveCaseItem($value); + $result[] = $case->resolveItem($value); } else { - $result[$case->resolveCaseItem($key)] = $case->resolveCaseItem($value); + $result[$case->resolveItem($key)] = $case->resolveItem($value); } } @@ -173,7 +173,7 @@ public function keyBy(callable|string $key): static $keyed = []; foreach ($this->cases as $case) { - $keyed[$case->resolveCaseItem($key)] = $case; + $keyed[$case->resolveItem($key)] = $case; } return new static($keyed); @@ -197,7 +197,7 @@ public function groupBy(callable|string $key): static $grouped = []; foreach ($this->cases as $case) { - $grouped[$case->resolveCaseItem($key)][] = $case; + $grouped[$case->resolveItem($key)][] = $case; } foreach ($grouped as $key => $cases) { @@ -215,7 +215,7 @@ public function groupBy(callable|string $key): static public function filter(callable|string $filter): static { /** @phpstan-ignore method.nonObject */ - $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->resolveCaseItem($filter) === true; + $callback = is_callable($filter) ? $filter : fn(mixed $case) => $case->resolveItem($filter) === true; return new static(array_filter($this->cases, $callback)); } @@ -269,7 +269,7 @@ public function sortBy(callable|string $key): static { $cases = $this->cases; - uasort($cases, fn(mixed $a, mixed $b) => $a->resolveCaseItem($key) <=> $b->resolveCaseItem($key)); + uasort($cases, fn(mixed $a, mixed $b) => $a->resolveItem($key) <=> $b->resolveItem($key)); return new static($cases); } @@ -299,7 +299,7 @@ public function sortByDesc(callable|string $key): static { $cases = $this->cases; - uasort($cases, fn(mixed $a, mixed $b) => $b->resolveCaseItem($key) <=> $a->resolveCaseItem($key)); + uasort($cases, fn(mixed $a, mixed $b) => $b->resolveItem($key) <=> $a->resolveItem($key)); return new static($cases); } diff --git a/src/Concerns/SelfAware.php b/src/Concerns/SelfAware.php index bbf3989..4fa8834 100644 --- a/src/Concerns/SelfAware.php +++ b/src/Concerns/SelfAware.php @@ -69,7 +69,7 @@ public static function metaNames(): array * @return TItemValue * @throws ValueError */ - public function resolveCaseItem(callable|string $item): mixed + public function resolveItem(callable|string $item): mixed { return match (true) { is_callable($item) => $item($this), diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index ad5ee11..74379ea 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -424,7 +424,7 @@ }); it('retrieves a case item') - ->expect(fn(string $item, mixed $value) => BackedEnum::one->resolveCaseItem($item) === $value) + ->expect(fn(string $item, mixed $value) => BackedEnum::one->resolveItem($item) === $value) ->toBeTrue() ->with([ ['name', 'one'], @@ -434,10 +434,10 @@ ]); it('retrieves the item of a case using a closure') - ->expect(BackedEnum::one->resolveCaseItem(fn(BackedEnum $case) => $case->color())) + ->expect(BackedEnum::one->resolveItem(fn(BackedEnum $case) => $case->color())) ->toBe('red'); -it('throws a value error when attempting to retrieve an invalid item', fn() => BackedEnum::one->resolveCaseItem('invalid')) +it('throws a value error when attempting to retrieve an invalid item', fn() => BackedEnum::one->resolveItem('invalid')) ->throws(ValueError::class, '"invalid" is not a valid meta for enum "Cerbero\Enum\BackedEnum"'); it('retrieves the value of a backed case or the name of a pure case', function() { diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index a9a8bb8..aaa2d12 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -441,7 +441,7 @@ }); it('retrieves the item of a case') - ->expect(fn(string $item, mixed $value) => PureEnum::one->resolveCaseItem($item) === $value) + ->expect(fn(string $item, mixed $value) => PureEnum::one->resolveItem($item) === $value) ->toBeTrue() ->with([ ['name', 'one'], @@ -450,10 +450,10 @@ ]); it('retrieves the item of a case using a closure') - ->expect(PureEnum::one->resolveCaseItem(fn(PureEnum $case) => $case->color())) + ->expect(PureEnum::one->resolveItem(fn(PureEnum $case) => $case->color())) ->toBe('red'); -it('throws a value error when attempting to retrieve an invalid item', fn() => PureEnum::one->resolveCaseItem('invalid')) +it('throws a value error when attempting to retrieve an invalid item', fn() => PureEnum::one->resolveItem('invalid')) ->throws(ValueError::class, '"invalid" is not a valid meta for enum "Cerbero\Enum\PureEnum"'); it('retrieves the value of a backed case or the name of a pure case', function() { From dc6505aef1fd0e6e6644d1d132b27238b15ee90c Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 5 Oct 2024 13:20:12 -0300 Subject: [PATCH 45/49] Update readme --- README.md | 95 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index a578a05..8cc6566 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ composer require cerbero/enum ## ๐Ÿ”ฎ Usage * [โš–๏ธ Comparison](#%EF%B8%8F-comparison) -* [๐Ÿ”‘ Keys](#-keys) +* [๐Ÿท๏ธ Meta](#-meta) * [๐Ÿšฐ Hydration](#-hydration) * [๐ŸŽฒ Enum operations](#-enum-operations) * [๐Ÿงบ Cases collection](#-cases-collection) @@ -106,27 +106,23 @@ BackedEnum::One->notIn(['One', 'four']); // true ``` -### ๐Ÿ”‘ Keys +### ๐Ÿท๏ธ Meta -With the term "key" we refer to any element defined for an enum case, such as its name, value or public methods: +Meta add extra information to a case. Meta can be added by implementing a public non-static method and/or by attaching `#[Meta]` attributes to cases: ```php enum BackedEnum: int { use Enumerates; + #[Meta(color: 'red', shape: 'triangle')] case One = 1; + + #[Meta(color: 'green', shape: 'square')] case Two = 2; - case Three = 3; - public function color(): string - { - return match ($this) { - self::One => 'red', - self::Two => 'green', - self::Three => 'blue', - }; - } + #[Meta(color: 'blue', shape: 'circle')] + case Three = 3; public function isOdd(): bool { @@ -135,12 +131,33 @@ enum BackedEnum: int } ``` -Keys provide extra information for an enum and can also be leveraged for the [hydration](#-hydration), [elaboration](#-enum-operations) and [collection](#-cases-collection) of cases. +The above enum has 3 meta defined for each case: `color`, `shape` and `isOdd`. Meta defined via the `#[Meta]` attribute are ideal to declare static information, whilst meta defined via public non-static methods are ideal to declare dynamic information. + +The `#[Meta]` attribute can also be attached to the enum itself to provide default meta values when a case does not declare its own meta value: + +```php +#[Meta(color: 'red', shape: 'triangle')] +enum BackedEnum: int +{ + use Enumerates; + + case One = 1; + + #[Meta(color: 'green', shape: 'square')] + case Two = 2; + + case Three = 3; +} +``` + +In the above example all cases have a `red` color and a `triangle` shape, except the case `Two` that overrides the default meta values of the enum. + +Meta can also be leveraged for the [hydration](#-hydration), [elaboration](#-enum-operations) and [collection](#-cases-collection) of cases. ### ๐Ÿšฐ Hydration -An enum case can be instantiated from its own name, value (if backed) or [keys](#-keys): +An enum case can be instantiated from its own name, value (if backed) or [meta](#-meta): ```php PureEnum::from('One'); // PureEnum::One @@ -151,16 +168,14 @@ PureEnum::fromName('One'); // PureEnum::One PureEnum::fromName('four'); // throws ValueError PureEnum::tryFromName('One'); // PureEnum::One PureEnum::tryFromName('four'); // null -PureEnum::fromKey('name', 'One'); // CasesCollection[PureEnum::One] -PureEnum::fromKey('value', 1); // throws ValueError -PureEnum::fromKey('color', 'red'); // CasesCollection[PureEnum::One] -PureEnum::fromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] -PureEnum::fromKey(fn(PureEnum $case) => $case->isOdd()); // CasesCollection[PureEnum::One, PureEnum::Three] -PureEnum::tryFromKey('name', 'One'); // CasesCollection[PureEnum::One] -PureEnum::tryFromKey('value', 1); // null -PureEnum::tryFromKey('color', 'red'); // CasesCollection[PureEnum::One] -PureEnum::tryFromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] -PureEnum::tryFromKey(fn(PureEnum $case) => $case->isOdd()); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::fromMeta('color', 'red'); // CasesCollection[PureEnum::One] +PureEnum::fromMeta('color', 'purple'); // throws ValueError +PureEnum::fromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::fromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::tryFromMeta('color', 'red'); // CasesCollection[PureEnum::One] +PureEnum::fromMeta('color', 'purple'); // null +PureEnum::tryFromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +PureEnum::tryFromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[PureEnum::One, PureEnum::Three] BackedEnum::from(1); // BackedEnum::One BackedEnum::from('1'); // throws ValueError @@ -170,19 +185,15 @@ BackedEnum::fromName('One'); // BackedEnum::One BackedEnum::fromName('four'); // throws ValueError BackedEnum::tryFromName('One'); // BackedEnum::One BackedEnum::tryFromName('four'); // null -BackedEnum::fromKey('name', 'One'); // CasesCollection[BackedEnum::One] -BackedEnum::fromKey('value', 1); // CasesCollection[BackedEnum::One] -BackedEnum::fromKey('color', 'red'); // CasesCollection[BackedEnum::One] -BackedEnum::fromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] -BackedEnum::fromKey(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection[BackedEnum::One, BackedEnum::Three] -BackedEnum::tryFromKey('name', 'One'); // CasesCollection[BackedEnum::One] -BackedEnum::tryFromKey('value', 1); // CasesCollection[BackedEnum::One] -BackedEnum::tryFromKey('color', 'red'); // CasesCollection[BackedEnum::One] -BackedEnum::tryFromKey('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] -BackedEnum::tryFromKey(fn(BackedEnum $case) => $case->isOdd()); // CasesCollection[BackedEnum::One, BackedEnum::Three] +BackedEnum::fromMeta('color', 'red'); // CasesCollection[BackedEnum::One] +BackedEnum::fromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +BackedEnum::fromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[BackedEnum::One, BackedEnum::Three] +BackedEnum::tryFromMeta('color', 'red'); // CasesCollection[BackedEnum::One] +BackedEnum::tryFromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] +BackedEnum::tryFromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[BackedEnum::One, BackedEnum::Three] ``` -Hydrating from keys may return multiple cases. To facilitate further processing, such cases are [collected into a `CasesCollection`](#-cases-collection). +Hydrating from meta can return multiple cases. To facilitate further processing, such cases are [collected into a `CasesCollection`](#-cases-collection). ### ๐ŸŽฒ Enum operations @@ -315,12 +326,12 @@ $case = BackedEnum::One; $case(); // 1 ``` -When calling an inaccessible method of a case, by default the value of the key matching the missing method is returned: +When calling an inaccessible method of a case, by default the value of the meta matching the missing method is returned: ```php PureEnum::One->color(); // 'red' -BackedEnum::One->color(); // 'red' +BackedEnum::One->shape(); // 'triangle' ``` To improve the autocompletion of our IDE, we can add some method annotations to our enums: @@ -378,12 +389,18 @@ Finally, the following methods can be useful for inspecting enums or auto-genera ```php PureEnum::isPure(); // true PureEnum::isBacked(); // false -PureEnum::keys(); // ['color', 'shape', 'isOdd'] +PureEnum::metaNames(); // ['color', 'shape', 'isOdd'] +PureEnum::One->resolveItem('name'); // 'One' +PureEnum::One->resolveMeta('isOdd'); // true +PureEnum::One->resolveMetaAttribute('color'); // 'red' PureEnum::One->value(); // 'One' BackedEnum::isPure(); // false BackedEnum::isBacked(); // true -BackedEnum::keys(); // ['color', 'shape', 'isOdd'] +BackedEnum::metaNames(); // ['color', 'shape', 'isOdd'] +BackedEnum::One->resolveItem('value'); // 1 +BackedEnum::One->resolveMeta('isOdd'); // true +BackedEnum::One->resolveMetaAttribute('color'); // 'red' BackedEnum::One->value(); // 1 ``` From c754e88e68363dddb9e3650915ad069fde68259c Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 5 Oct 2024 13:22:26 -0300 Subject: [PATCH 46/49] Update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8cc6566..98b310d 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ PureEnum::fromMeta('color', 'purple'); // throws ValueError PureEnum::fromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] PureEnum::fromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[PureEnum::One, PureEnum::Three] PureEnum::tryFromMeta('color', 'red'); // CasesCollection[PureEnum::One] -PureEnum::fromMeta('color', 'purple'); // null +PureEnum::tryFromMeta('color', 'purple'); // null PureEnum::tryFromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] PureEnum::tryFromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[PureEnum::One, PureEnum::Three] @@ -189,6 +189,7 @@ BackedEnum::fromMeta('color', 'red'); // CasesCollection[BackedEnum::One] BackedEnum::fromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] BackedEnum::fromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[BackedEnum::One, BackedEnum::Three] BackedEnum::tryFromMeta('color', 'red'); // CasesCollection[BackedEnum::One] +BackedEnum::tryFromMeta('color', 'purple'); // null BackedEnum::tryFromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] BackedEnum::tryFromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[BackedEnum::One, BackedEnum::Three] ``` From d28fae59fdd8381075a67338973814576e90c3b5 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 5 Oct 2024 15:32:05 -0300 Subject: [PATCH 47/49] Update readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 98b310d..995be4b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ composer require cerbero/enum ## ๐Ÿ”ฎ Usage * [โš–๏ธ Comparison](#%EF%B8%8F-comparison) -* [๐Ÿท๏ธ Meta](#-meta) +* [๐Ÿท๏ธ Meta](%EF%B8%8F-meta) * [๐Ÿšฐ Hydration](#-hydration) * [๐ŸŽฒ Enum operations](#-enum-operations) * [๐Ÿงบ Cases collection](#-cases-collection) @@ -73,7 +73,7 @@ BackedEnum::doesntHave(1); // false BackedEnum::doesntHave(4); // true ``` -Otherwise we can check whether cases match with a given name or value: +Otherwise we can check whether cases match a given name or value: ```php PureEnum::One->is('One'); // true @@ -131,9 +131,9 @@ enum BackedEnum: int } ``` -The above enum has 3 meta defined for each case: `color`, `shape` and `isOdd`. Meta defined via the `#[Meta]` attribute are ideal to declare static information, whilst meta defined via public non-static methods are ideal to declare dynamic information. +The above enum defines 3 meta for each case: `color`, `shape` and `isOdd`. The `#[Meta]` attributes are ideal to declare static information, whilst public non-static methods are ideal to declare dynamic information. -The `#[Meta]` attribute can also be attached to the enum itself to provide default meta values when a case does not declare its own meta value: +`#[Meta]` attributes can also be attached to the enum itself to provide default values when a case does not declare its own meta values: ```php #[Meta(color: 'red', shape: 'triangle')] @@ -150,14 +150,14 @@ enum BackedEnum: int } ``` -In the above example all cases have a `red` color and a `triangle` shape, except the case `Two` that overrides the default meta values of the enum. +In the above example all cases have a `red` color and a `triangle` shape, except the case `Two` that overrides the default meta values. Meta can also be leveraged for the [hydration](#-hydration), [elaboration](#-enum-operations) and [collection](#-cases-collection) of cases. ### ๐Ÿšฐ Hydration -An enum case can be instantiated from its own name, value (if backed) or [meta](#-meta): +An enum case can be instantiated from its own name, value (if backed) or [meta](%EF%B8%8F-meta): ```php PureEnum::from('One'); // PureEnum::One From 97e1db8e2cae695fff4a2dc20d5ae30fdce9772f Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 5 Oct 2024 15:51:44 -0300 Subject: [PATCH 48/49] Update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 995be4b..6bdbd6a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ composer require cerbero/enum ## ๐Ÿ”ฎ Usage * [โš–๏ธ Comparison](#%EF%B8%8F-comparison) -* [๐Ÿท๏ธ Meta](%EF%B8%8F-meta) +* [๐Ÿท๏ธ Meta](#%EF%B8%8F-meta) * [๐Ÿšฐ Hydration](#-hydration) * [๐ŸŽฒ Enum operations](#-enum-operations) * [๐Ÿงบ Cases collection](#-cases-collection) @@ -157,7 +157,7 @@ Meta can also be leveraged for the [hydration](#-hydration), [elaboration](#-enu ### ๐Ÿšฐ Hydration -An enum case can be instantiated from its own name, value (if backed) or [meta](%EF%B8%8F-meta): +An enum case can be instantiated from its own name, value (if backed) or [meta](#%EF%B8%8F-meta): ```php PureEnum::from('One'); // PureEnum::One @@ -186,6 +186,7 @@ BackedEnum::fromName('four'); // throws ValueError BackedEnum::tryFromName('One'); // BackedEnum::One BackedEnum::tryFromName('four'); // null BackedEnum::fromMeta('color', 'red'); // CasesCollection[BackedEnum::One] +BackedEnum::fromMeta('color', 'purple'); // throws ValueError BackedEnum::fromMeta('isOdd'); // CasesCollection[PureEnum::One, PureEnum::Three] BackedEnum::fromMeta('shape', fn(string $shape) => in_array($shape, ['square', 'circle'])); // CasesCollection[BackedEnum::One, BackedEnum::Three] BackedEnum::tryFromMeta('color', 'red'); // CasesCollection[BackedEnum::One] @@ -356,7 +357,7 @@ enum BackedEnum: int } ``` -Depending on our needs, we can customize the default magic behavior of all enums in our application and run our own custom logic when invoking a case or calling inaccessible methods: +Depending on our needs, we can customize the default behavior of all enums in our application when invoking a case or calling inaccessible methods: ```php use Cerbero\Enum\Enums; From c5f5c9021a5eb53641a625e3a4ea15be13bb09ee Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sat, 5 Oct 2024 15:56:46 -0300 Subject: [PATCH 49/49] Update changelog --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf7906b..12861df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,15 @@ All notable changes to `enum` will be documented in this file. Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + ## NEXT - YYYY-MM-DD ### Added - Nothing +### Changed +- Nothing + ### Deprecated - Nothing @@ -22,6 +26,33 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi - Nothing +## 2.0.0 - 2024-10-05 + +### Added +- Custom and default implementation of magic methods +- The `Meta` attribute and related methods +- Method `value()` to get the value of a backed case or the name of a pure case +- Methods `toArray()`, `map()` to the `CasesCollection` +- Generics in docblocks +- Static analysis + +### Changed +- Renamed keys to meta +- `CasesCollection` methods return an instance of the collection whenever possible +- `CasesCollection::groupBy()` groups into instances of the collection +- Filtering methods keep the collection keys +- Renamed methods `CollectsCases::casesBy*()` to `CollectsCases::keyBy*()` +- Renamed `cases()` to `all()` in `CasesCollection` +- Renamed `get()` to `resolveMeta()` in `SelfAware` +- When hydrating from meta, the value is no longer mandatory and it defaults to `true` +- The value for `pluck()` is now mandatory +- Renamed sorting methods +- Introduced PER code style + +### Removed +- Parameter `$default` from the `CasesCollection::first()` method + + ## 1.0.0 - 2022-07-12 ### Added