From 8013c08846bb61db27bcef29665c0b7d9497400d Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Fri, 8 Jul 2022 17:30:47 +0200 Subject: [PATCH 01/21] Create tests --- tests/BackedEnum.php | 60 +++++++ tests/BackedEnumTest.php | 348 ++++++++++++++++++++++++++++++++++++++ tests/PureEnum.php | 60 +++++++ tests/PureEnumTest.php | 353 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 821 insertions(+) create mode 100644 tests/BackedEnum.php create mode 100644 tests/BackedEnumTest.php create mode 100644 tests/PureEnum.php create mode 100644 tests/PureEnumTest.php diff --git a/tests/BackedEnum.php b/tests/BackedEnum.php new file mode 100644 index 0000000..5fb49de --- /dev/null +++ b/tests/BackedEnum.php @@ -0,0 +1,60 @@ + 'red', + static::two => 'green', + static::three => 'blue', + }; + } + + /** + * Retrieve the shape of the case + * + * @return string + */ + public function shape(): string + { + return match ($this) { + static::one => 'triangle', + static::two => 'square', + static::three => 'circle', + }; + } + + /** + * Retrieve whether the case is odd + * + * @return bool + */ + public function odd(): bool + { + return match ($this) { + static::one => true, + static::two => false, + static::three => true, + }; + } +} diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php new file mode 100644 index 0000000..8caf2ec --- /dev/null +++ b/tests/BackedEnumTest.php @@ -0,0 +1,348 @@ +expect(BackedEnum::isPure()) + ->toBeFalse(); + +it('determines whether the enum is backed') + ->expect(BackedEnum::isBacked()) + ->toBeTrue(); + +it('retrieves all the names of the cases') + ->expect(BackedEnum::names()) + ->toBe(['one', 'two', 'three']); + +it('retrieves all the values of the backed cases') + ->expect(BackedEnum::values()) + ->toBe([1, 2, 3]); + +it('retrieves all the keys of the cases') + ->expect(BackedEnum::keys('color')) + ->toBe(['red', 'green', 'blue']); + +it('throws a value error when requesting an invalid key', fn () => BackedEnum::keys('invalid')) + ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\BackedEnum"'); + +it('retrieves a collection with all the cases') + ->expect(BackedEnum::collect()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); + +it('retrieves all cases keyed by name') + ->expect(BackedEnum::casesByName()) + ->toBe(['one' => BackedEnum::one, 'two' => BackedEnum::two, 'three' => BackedEnum::three]); + +it('retrieves all cases keyed by value') + ->expect(BackedEnum::casesByValue()) + ->toBe([1 => BackedEnum::one, 2 => BackedEnum::two, 3 => BackedEnum::three]); + +it('retrieves all cases keyed by a custom key') + ->expect(BackedEnum::casesBy('color')) + ->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]); + +it('retrieves a collection with the filtered cases') + ->expect(BackedEnum::filter(fn (UnitEnum $case) => $case->name !== 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::two]); + +it('retrieves a collection of cases having the given names') + ->expect(BackedEnum::only('two', 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::two, 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]); + +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]); + +it('retrieves a collection of backed cases not having the given values') + ->expect(BackedEnum::exceptValues(1, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::two]); + +it('retrieves an array of values') + ->expect(BackedEnum::pluck()) + ->toBe([1, 2, 3]); + +it('retrieves an array of custom values') + ->expect(BackedEnum::pluck('color')) + ->toBe(['red', 'green', 'blue']); + +it('retrieves an associative array with custom keys and values') + ->expect(BackedEnum::pluck('color', 'shape')) + ->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())) + ->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) + ->toBeTrue() + ->with([ + [BackedEnum::one, true], + [new stdClass(), false], + ['one', false], + ['four', false], + [1, true], + [4, false], + ['1', false], + ['4', false], + ]); + +it('determines whether an enum does not have a target') + ->expect(fn (mixed $target, bool $result) => BackedEnum::doesntHave($target) === $result) + ->toBeTrue() + ->with([ + [BackedEnum::one, false], + [new stdClass(), true], + ['one', true], + ['four', true], + [1, false], + [4, true], + ['1', true], + ['4', true], + ]); + +it('determines whether an enum case matches a target') + ->expect(fn (mixed $target, bool $result) => BackedEnum::one->is($target) === $result) + ->toBeTrue() + ->with([ + [BackedEnum::one, true], + [BackedEnum::two, false], + ['one', false], + ['two', false], + [1, true], + [2, false], + ['1', false], + ['2', false], + ]); + +it('determines whether an enum case does not match a target') + ->expect(fn (mixed $target, bool $result) => BackedEnum::one->isNot($target) === $result) + ->toBeTrue() + ->with([ + [BackedEnum::one, false], + [BackedEnum::two, true], + ['one', true], + ['two', true], + [1, false], + [2, true], + ['1', true], + ['2', true], + ]); + +it('determines whether an enum case matches some targets') + ->expect(fn (mixed $targets, bool $result) => BackedEnum::one->in($targets) === $result) + ->toBeTrue() + ->with([ + [[BackedEnum::one, BackedEnum::two], true], + [[BackedEnum::two, BackedEnum::three], false], + [['one', 'two'], false], + [['two', 'three'], false], + [[1, 2], true], + [[2, 3], false], + [['1', '2'], false], + [['2', '3'], false], + ]); + +it('determines whether an enum case does not match any target') + ->expect(fn (mixed $targets, bool $result) => BackedEnum::one->notIn($targets) === $result) + ->toBeTrue() + ->with([ + [[BackedEnum::one, BackedEnum::two], false], + [[BackedEnum::two, BackedEnum::three], true], + [['one', 'two'], true], + [['two', 'three'], true], + [[1, 2], false], + [[2, 3], true], + [['1', '2'], true], + [['2', '3'], true], + ]); + +it('retrieves a collection of cases sorted by name ascending') + ->expect(BackedEnum::sort()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::three, 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]); + +it('retrieves a collection of cases sorted by value ascending') + ->expect(BackedEnum::sortByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); + +it('retrieves a collection of cases sorted by value descending') + ->expect(BackedEnum::sortDescByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::three, BackedEnum::two, 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]); + +it('retrieves a collection of cases sorted by a custom value descending') + ->expect(BackedEnum::sortDescBy('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())) + ->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())) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); + +it('retrieves the count of cases') + ->expect(BackedEnum::count()) + ->toBe(3); + +it('retrieves the case hydrated from a value') + ->expect(fn (int $value, BackedEnum $case) => BackedEnum::from($value) === $case) + ->toBeTrue() + ->with([ + [1, BackedEnum::one], + [2, BackedEnum::two], + [3, BackedEnum::three], + ]); + +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) + ->toBeTrue() + ->not->toThrow(ValueError::class) + ->with([ + [1, BackedEnum::one], + [2, BackedEnum::two], + [3, BackedEnum::three], + [4, null], + ]); + +it('retrieves the case hydrated from a name') + ->expect(fn (string $name, BackedEnum $case) => BackedEnum::fromName($name) === $case) + ->toBeTrue() + ->with([ + ['one', BackedEnum::one], + ['two', BackedEnum::two], + ['three', BackedEnum::three], + ]); + +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) + ->toBeTrue() + ->not->toThrow(ValueError::class) + ->with([ + ['one', BackedEnum::one], + ['two', BackedEnum::two], + ['three', BackedEnum::three], + ['four', null], + ]); + +it('retrieves the case hydrated from a key') + ->expect(fn (callable|string $key, mixed $value, BackedEnum $case) => BackedEnum::fromKey($key, $value) === $case) + ->toBeTrue() + ->with([ + ['color', 'red', BackedEnum::one], + ['name', 'three', BackedEnum::three], + ]); + +it('retrieves the case hydrated from a key using a closure') + ->expect(BackedEnum::fromKey(fn (BackedEnum $case) => $case->shape(), 'square')) + ->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 (callable|string $key, mixed $value, ?BackedEnum $case) => BackedEnum::tryFromKey($key, $value) === $case) + ->toBeTrue() + ->not->toThrow(ValueError::class) + ->with([ + ['color', 'red', BackedEnum::one], + ['name', 'three', 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')) + ->toBe(BackedEnum::two); + +it('retrieves the key of a case') + ->expect(fn (string $key, mixed $value) => BackedEnum::one->get($key) === $value) + ->toBeTrue() + ->with([ + ['name', 'one'], + ['value', 1], + ['color', 'red'], + ['shape', 'triangle'], + ]); + +it('retrieves the key of a case using a closure') + ->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')) + ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\BackedEnum"'); + +it('retrieves the case hydrated from a key dynamically') + ->expect(BackedEnum::fromColor('red')) + ->toBe(BackedEnum::one); + +it('retrieves all cases hydrated from a key dynamically') + ->expect(BackedEnum::fromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::three]); + +it('throws a value error when hydrating cases with an invalid key dynamically', fn () => BackedEnum::fromOdd(123)) + ->throws(ValueError::class, 'Invalid value for the key "odd" for enum "Cerbero\Enum\BackedEnum"'); + +it('attempts to retrieve the case hydrated from a key dynamically') + ->expect(BackedEnum::tryFromColor('red')) + ->toBe(BackedEnum::one) + ->and(BackedEnum::tryFromColor('violet')) + ->toBeNull(); + +it('attempts to retrieve all cases hydrated from a key dynamically') + ->expect(BackedEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::three]); diff --git a/tests/PureEnum.php b/tests/PureEnum.php new file mode 100644 index 0000000..fd05bb3 --- /dev/null +++ b/tests/PureEnum.php @@ -0,0 +1,60 @@ + 'red', + static::two => 'green', + static::three => 'blue', + }; + } + + /** + * Retrieve the shape of the case + * + * @return string + */ + public function shape(): string + { + return match ($this) { + static::one => 'triangle', + static::two => 'square', + static::three => 'circle', + }; + } + + /** + * Retrieve whether the case is odd + * + * @return bool + */ + public function odd(): bool + { + return match ($this) { + static::one => true, + static::two => false, + static::three => true, + }; + } +} diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php new file mode 100644 index 0000000..88c2373 --- /dev/null +++ b/tests/PureEnumTest.php @@ -0,0 +1,353 @@ +expect(PureEnum::isPure()) + ->toBeTrue(); + +it('determines whether the enum is backed') + ->expect(PureEnum::isBacked()) + ->toBeFalse(); + +it('retrieves all the names of the cases') + ->expect(PureEnum::names()) + ->toBe(['one', 'two', 'three']); + +it('retrieves all the values of the backed cases') + ->expect(PureEnum::values()) + ->toBeEmpty(); + +it('retrieves all the keys of the cases') + ->expect(PureEnum::keys('color')) + ->toBe(['red', 'green', 'blue']); + +it('throws a value error when requesting an invalid key', fn () => PureEnum::keys('invalid')) + ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); + +it('retrieves a collection with all the cases') + ->expect(PureEnum::collect()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); + +it('retrieves all cases keyed by name') + ->expect(PureEnum::casesByName()) + ->toBe(['one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three]); + +it('retrieves all cases keyed by value') + ->expect(PureEnum::casesByValue()) + ->toBeEmpty(); + +it('retrieves all cases keyed by a custom key') + ->expect(PureEnum::casesBy('color')) + ->toBe(['red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three]); + +it('retrieves all cases keyed by the result of a closure') + ->expect(PureEnum::casesBy(fn (PureEnum $case) => $case->shape())) + ->toBe(['triangle' => PureEnum::one, 'square' => PureEnum::two, 'circle' => PureEnum::three]); + +it('retrieves a collection with the filtered cases') + ->expect(PureEnum::filter(fn (UnitEnum $case) => $case->name !== 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::two]); + +it('retrieves a collection of cases having the given names') + ->expect(PureEnum::only('two', 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two, PureEnum::three]); + +it('retrieves a collection of cases not having the given names') + ->expect(PureEnum::except('one', 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two]); + +it('retrieves a collection of backed cases having the given values') + ->expect(PureEnum::onlyValues(2, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); + +it('retrieves a collection of backed cases not having the given values') + ->expect(PureEnum::exceptValues(1, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); + +it('retrieves an array of values') + ->expect(PureEnum::pluck()) + ->toBe(['one', 'two', 'three']); + +it('retrieves an array of custom values') + ->expect(PureEnum::pluck('color')) + ->toBe(['red', 'green', 'blue']); + +it('retrieves an associative array with custom keys and values') + ->expect(PureEnum::pluck('color', 'shape')) + ->toBe(['triangle' => 'red', 'square' => 'green', 'circle' => 'blue']); + +it('retrieves an associative array with keys and values resolved from closures') + ->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) + ->toBeTrue() + ->with([ + [PureEnum::one, true], + [new stdClass(), false], + ['one', true], + ['four', false], + [1, false], + [4, false], + ['1', false], + ['4', false], + ]); + +it('determines whether an enum does not have a target') + ->expect(fn (mixed $target, bool $result) => PureEnum::doesntHave($target) === $result) + ->toBeTrue() + ->with([ + [PureEnum::one, false], + [new stdClass(), true], + ['one', false], + ['four', true], + [1, true], + [4, true], + ['1', true], + ['4', true], + ]); + +it('determines whether an enum case matches a target') + ->expect(fn (mixed $target, bool $result) => PureEnum::one->is($target) === $result) + ->toBeTrue() + ->with([ + [PureEnum::one, true], + [PureEnum::two, false], + ['one', true], + ['two', false], + [1, false], + [2, false], + ['1', false], + ['2', false], + ]); + +it('determines whether an enum case does not match a target') + ->expect(fn (mixed $target, bool $result) => PureEnum::one->isNot($target) === $result) + ->toBeTrue() + ->with([ + [PureEnum::one, false], + [PureEnum::two, true], + ['one', false], + ['two', true], + [1, true], + [2, true], + ['1', true], + ['2', true], + ]); + +it('determines whether an enum case matches some targets') + ->expect(fn (mixed $targets, bool $result) => PureEnum::one->in($targets) === $result) + ->toBeTrue() + ->with([ + [[PureEnum::one, PureEnum::two], true], + [[PureEnum::two, PureEnum::three], false], + [['one', 'two'], true], + [['two', 'three'], false], + [[1, 2], false], + [[2, 3], false], + [['1', '2'], false], + [['2', '3'], false], + ]); + +it('determines whether an enum case does not match any target') + ->expect(fn (mixed $targets, bool $result) => PureEnum::one->notIn($targets) === $result) + ->toBeTrue() + ->with([ + [[PureEnum::one, PureEnum::two], false], + [[PureEnum::two, PureEnum::three], true], + [['one', 'two'], false], + [['two', 'three'], true], + [[1, 2], true], + [[2, 3], true], + [['1', '2'], true], + [['2', '3'], true], + ]); + +it('retrieves a collection of cases sorted by name ascending') + ->expect(PureEnum::sort()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::three, PureEnum::two]); + +it('retrieves a collection of cases sorted by name descending') + ->expect(PureEnum::sortDesc()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two, PureEnum::three, PureEnum::one]); + +it('retrieves a collection of cases sorted by value ascending') + ->expect(PureEnum::sortByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); + +it('retrieves a collection of cases sorted by value descending') + ->expect(PureEnum::sortDescByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); + +it('retrieves a collection of cases sorted by a custom value ascending') + ->expect(PureEnum::sortBy('color')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::three, PureEnum::two, PureEnum::one]); + +it('retrieves a collection of cases sorted by a custom value descending') + ->expect(PureEnum::sortDescBy('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') + ->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') + ->expect(PureEnum::sortDescBy(fn (PureEnum $case) => $case->shape())) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); + +it('retrieves the count of cases') + ->expect(PureEnum::count()) + ->toBe(3); + +it('retrieves the case hydrated from a value') + ->expect(fn (string $value, PureEnum $case) => PureEnum::from($value) === $case) + ->toBeTrue() + ->with([ + ['one', PureEnum::one], + ['two', PureEnum::two], + ['three', PureEnum::three], + ]); + +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) + ->toBeTrue() + ->not->toThrow(ValueError::class) + ->with([ + ['one', PureEnum::one], + ['two', PureEnum::two], + ['three', PureEnum::three], + ['four', null], + ]); + +it('retrieves the case hydrated from a name') + ->expect(fn (string $name, PureEnum $case) => PureEnum::fromName($name) === $case) + ->toBeTrue() + ->with([ + ['one', PureEnum::one], + ['two', PureEnum::two], + ['three', PureEnum::three], + ]); + +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) + ->toBeTrue() + ->not->toThrow(ValueError::class) + ->with([ + ['one', PureEnum::one], + ['two', PureEnum::two], + ['three', PureEnum::three], + ['four', null], + ]); + +it('retrieves the case hydrated from a key') + ->expect(fn (callable|string $key, mixed $value, PureEnum $case) => PureEnum::fromKey($key, $value) === $case) + ->toBeTrue() + ->with([ + ['color', 'red', PureEnum::one], + ['name', 'three', PureEnum::three], + ]); + +it('retrieves all cases hydrated from a key') + ->expect(PureEnum::fromKey('odd', true)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::three]); + +it('retrieves the case hydrated from a key using a closure') + ->expect(PureEnum::fromKey(fn (PureEnum $case) => $case->shape(), 'square')) + ->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 (callable|string $key, mixed $value, ?PureEnum $case) => PureEnum::tryFromKey($key, $value) === $case) + ->toBeTrue() + ->not->toThrow(ValueError::class) + ->with([ + ['color', 'red', PureEnum::one], + ['name', 'three', 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')) + ->toBe(PureEnum::two); + +it('retrieves the key of a case') + ->expect(fn (string $key, mixed $value) => PureEnum::one->get($key) === $value) + ->toBeTrue() + ->with([ + ['name', 'one'], + ['color', 'red'], + ['shape', 'triangle'], + ]); + +it('retrieves the key of a case using a closure') + ->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')) + ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); + +it('retrieves the case hydrated from a key dynamically') + ->expect(PureEnum::fromColor('red')) + ->toBe(PureEnum::one); + +it('retrieves all cases hydrated from a key dynamically') + ->expect(PureEnum::fromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::three]); + +it('throws a value error when hydrating cases with an invalid key dynamically', fn () => PureEnum::fromOdd(123)) + ->throws(ValueError::class, 'Invalid value for the key "odd" for enum "Cerbero\Enum\PureEnum"'); + +it('attempts to retrieve the case hydrated from a key dynamically') + ->expect(PureEnum::tryFromColor('red')) + ->toBe(PureEnum::one) + ->and(PureEnum::tryFromColor('violet')) + ->toBeNull(); + +it('attempts to retrieve all cases hydrated from a key dynamically') + ->expect(PureEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::three]); From f2c31e6b8601a96205d886f0d2e8e777407c7ed6 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Fri, 8 Jul 2022 17:31:19 +0200 Subject: [PATCH 02/21] Implement functionalities --- src/CasesCollection.php | 268 +++++++++++++++++++++++++++++++++ src/Concerns/CollectsCases.php | 223 +++++++++++++++++++++++++++ src/Concerns/Compares.php | 100 ++++++++++++ src/Concerns/Enumerates.php | 16 ++ src/Concerns/Hydrates.php | 127 ++++++++++++++++ src/Concerns/KeysAware.php | 30 ++++ src/Concerns/SelfAware.php | 32 ++++ 7 files changed, 796 insertions(+) create mode 100644 src/CasesCollection.php create mode 100644 src/Concerns/CollectsCases.php create mode 100644 src/Concerns/Compares.php create mode 100644 src/Concerns/Enumerates.php create mode 100644 src/Concerns/Hydrates.php create mode 100644 src/Concerns/KeysAware.php create mode 100644 src/Concerns/SelfAware.php diff --git a/src/CasesCollection.php b/src/CasesCollection.php new file mode 100644 index 0000000..e6aa4d8 --- /dev/null +++ b/src/CasesCollection.php @@ -0,0 +1,268 @@ +enumIsBacked = ($cases[0] ?? null) instanceof BackedEnum; + } + + /** + * Retrieve the cases + * + * @return array + */ + public function cases(): array + { + return $this->cases; + } + + /** + * Retrieve the cases keyed by name + * + * @return array + */ + public function keyByName(): array + { + return $this->keyBy('name'); + } + + /** + * Retrieve the cases keyed by the given key + * + * @param callable|string $key + * @return array + */ + public function keyBy(callable|string $key): array + { + $result = []; + + foreach ($this->cases as $case) { + $result[$case->get($key)] = $case; + } + + return $result; + } + + /** + * Retrieve the cases keyed by value + * + * @return array + */ + public function keyByValue(): array + { + return $this->enumIsBacked ? $this->keyBy('value') : []; + } + + /** + * Retrieve all the names of the cases + * + * @return array + */ + public function names(): array + { + return array_column($this->cases, 'name'); + } + + /** + * Retrieve all the values of the backed cases + * + * @return array + */ + public function values(): array + { + return array_column($this->cases, 'value'); + } + + /** + * Retrieve all the keys of the cases + * + * @param callable|string $key + * @return array + */ + public function keys(callable|string $key): array + { + return $this->pluck($key); + } + + /** + * Retrieve an array of values optionally keyed by the given key + * + * @param callable|string|null $value + * @param callable|string|null $key + * @return array + */ + public function pluck(callable|string $value = null, callable|string $key = null): array + { + $result = []; + $value ??= $this->enumIsBacked ? 'value' : 'name'; + + foreach ($this->cases as $case) { + if ($key === null) { + $result[] = $case->get($value); + } else { + $result[$case->get($key)] = $case->get($value); + } + } + + return $result; + } + + /** + * Retrieve a collection with the filtered cases + * + * @param callable $callback + * @return static + */ + public function filter(callable $callback): static + { + $cases = array_filter($this->cases, $callback); + + return new static(array_values($cases)); + } + + /** + * Retrieve a collection of cases having the given names + * + * @param string ...$name + * @return static + */ + public function only(string ...$name): static + { + return $this->filter(fn (UnitEnum $enum) => in_array($enum->name, $name)); + } + + /** + * Retrieve a collection of cases not having the given names + * + * @param string ...$name + * @return static + */ + public function except(string ...$name): static + { + return $this->filter(fn (UnitEnum $enum) => !in_array($enum->name, $name)); + } + + /** + * Retrieve a collection of backed cases having the given values + * + * @param string|int ...$value + * @return static + */ + public function onlyValues(string|int ...$value): static + { + return $this->filter(fn (UnitEnum $enum) => $this->enumIsBacked && in_array($enum->value, $value)); + } + + /** + * Retrieve a collection of backed cases not having the given values + * + * @param string|int ...$value + * @return static + */ + public function exceptValues(string|int ...$value): static + { + return $this->filter(fn (UnitEnum $enum) => $this->enumIsBacked && !in_array($enum->value, $value)); + } + + /** + * Retrieve a collection of cases sorted by name ascending + * + * @return static + */ + public function sort(): static + { + return $this->sortBy('name'); + } + + /** + * Retrieve a collection of cases sorted by name descending + * + * @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 + */ + public function sortBy(callable|string $key): static + { + $cases = $this->cases; + + usort($cases, fn ($a, $b) => $a->get($key) <=> $b->get($key)); + + return new static($cases); + } + + /** + * Retrieve a collection of cases sorted by the given key descending + * + * @param callable|string $key + * @return static + */ + public function sortDescBy(callable|string $key): static + { + $cases = $this->cases; + + usort($cases, fn ($a, $b) => $a->get($key) > $b->get($key) ? -1 : 1); + + return new static($cases); + } + + /** + * Retrieve a collection of cases sorted by value ascending + * + * @return static + */ + public function sortByValue(): static + { + return $this->enumIsBacked ? $this->sortBy('value') : new static([]); + } + + /** + * Retrieve a collection of cases sorted by value descending + * + * @return static + */ + public function sortDescByValue(): static + { + return $this->enumIsBacked ? $this->sortDescBy('value') : new static([]); + } + + /** + * Retrieve the count of cases + * + * @return int + */ + public function count(): int + { + return count($this->cases); + } +} diff --git a/src/Concerns/CollectsCases.php b/src/Concerns/CollectsCases.php new file mode 100644 index 0000000..6913366 --- /dev/null +++ b/src/Concerns/CollectsCases.php @@ -0,0 +1,223 @@ + + */ + public static function casesByName(): array + { + return static::collect()->keyByName(); + } + + /** + * Retrieve all cases keyed by value + * + * @return array + */ + public static function casesByValue(): array + { + return static::collect()->keyByValue(); + } + + /** + * Retrieve all cases keyed by the given key + * + * @param callable|string $key + * @return array + */ + public static function casesBy(callable|string $key): array + { + return static::collect()->keyBy($key); + } + + /** + * Retrieve all the names of the cases + * + * @return array + */ + public static function names(): array + { + return static::collect()->names(); + } + + /** + * Retrieve all the values of the backed cases + * + * @return array + */ + public static function values(): array + { + return static::collect()->values(); + } + + /** + * Retrieve all the keys of the backed cases + * + * @param callable|string $key + * @return array + */ + public static function keys(callable|string $key): array + { + return static::collect()->keys($key); + } + + /** + * Retrieve a collection with the filtered cases + * + * @param callable $callback + * @return CasesCollection + */ + public static function filter(callable $callback): CasesCollection + { + return static::collect()->filter($callback); + } + + /** + * Retrieve a collection of cases having the given names + * + * @param string ...$name + * @return CasesCollection + */ + public static function only(string ...$name): CasesCollection + { + return static::collect()->only(...$name); + } + + /** + * Retrieve a collection of cases not having the given names + * + * @param string ...$name + * @return CasesCollection + */ + public static function except(string ...$name): CasesCollection + { + return static::collect()->except(...$name); + } + + /** + * Retrieve a collection of backed cases having the given values + * + * @param string|int ...$value + * @return CasesCollection + */ + public static function onlyValues(string|int ...$value): CasesCollection + { + return static::collect()->onlyValues(...$value); + } + + /** + * Retrieve a collection of backed cases not having the given values + * + * @param string|int ...$value + * @return CasesCollection + */ + public static function exceptValues(string|int ...$value): CasesCollection + { + return static::collect()->exceptValues(...$value); + } + + /** + * Retrieve an array of values optionally keyed by the given key + * + * @param callable|string|null $value + * @param callable|string|null $key + * @return array + */ + public static function pluck(callable|string $value = null, callable|string $key = null): array + { + return static::collect()->pluck($value, $key); + } + + /** + * Retrieve a collection of cases sorted by name ascending + * + * @return CasesCollection + */ + public static function sort(): CasesCollection + { + return static::collect()->sort(); + } + + /** + * Retrieve a collection of cases sorted by name descending + * + * @return CasesCollection + */ + public static function sortDesc(): CasesCollection + { + return static::collect()->sortDesc(); + } + + /** + * Retrieve a collection of cases sorted by value ascending + * + * @return CasesCollection + */ + public static function sortByValue(): CasesCollection + { + return static::collect()->sortByValue(); + } + + /** + * Retrieve a collection of cases sorted by value descending + * + * @return CasesCollection + */ + public static function sortDescByValue(): CasesCollection + { + return static::collect()->sortDescByValue(); + } + + /** + * Retrieve a collection of cases sorted by the given key ascending + * + * @param callable|string $key + * @return CasesCollection + */ + public static function sortBy(callable|string $key): CasesCollection + { + return static::collect()->sortBy($key); + } + + /** + * Retrieve a collection of cases sorted by the given key descending + * + * @param callable|string $key + * @return CasesCollection + */ + public static function sortDescBy(callable|string $key): CasesCollection + { + return static::collect()->sortDescBy($key); + } + + /** + * Retrieve the count of cases + * + * @return int + */ + public static function count(): int + { + return static::collect()->count(); + } +} diff --git a/src/Concerns/Compares.php b/src/Concerns/Compares.php new file mode 100644 index 0000000..73fb861 --- /dev/null +++ b/src/Concerns/Compares.php @@ -0,0 +1,100 @@ +is($target)) { + return true; + } + } + + return false; + } + + /** + * Determine whether the enum does not have the given target + * + * @param mixed $target + * @return bool + */ + public static function doesntHave(mixed $target): bool + { + foreach (static::cases() as $case) { + if ($case->is($target)) { + return false; + } + } + + return true; + } + + /** + * Determine whether the current case matches the given target + * + * @param mixed $target + * @return bool + */ + public function is(mixed $target): bool + { + return in_array($target, [$this, static::isPure() ? $this->name : $this->value], true); + } + + /** + * Determine whether the current case does not match the given target + * + * @param mixed $target + * @return bool + */ + public function isNot(mixed $target): bool + { + return !$this->is($target); + } + + /** + * Determine whether the current case matches at least one of the given targets + * + * @param iterable $targets + * @return bool + */ + public function in(iterable $targets): bool + { + foreach ($targets as $target) { + if ($this->is($target)) { + return true; + } + } + + return false; + } + + /** + * Determine whether the current case does not match any of the given targets + * + * @param iterable $targets + * @return bool + */ + public function notIn(iterable $targets): bool + { + foreach ($targets as $target) { + if ($this->is($target)) { + return false; + } + } + + return true; + } +} diff --git a/src/Concerns/Enumerates.php b/src/Concerns/Enumerates.php new file mode 100644 index 0000000..3ca0e57 --- /dev/null +++ b/src/Concerns/Enumerates.php @@ -0,0 +1,16 @@ +name === $name) { + return $case; + } + } + + return null; + } + + /** + * Retrieve cases hydrated from the given key + * + * @param callable|string $key + * @param mixed $value + * @return CasesCollection|static + * @throws ValueError + */ + public static function fromKey(callable|string $key, mixed $value): CasesCollection|static + { + if ($result = static::tryFromKey($key, $value)) { + return $result; + } + + $target = is_callable($key) ? 'given callable key' : "key \"$key\""; + + throw new ValueError(sprintf('Invalid value for the %s for enum "%s"', $target, static::class)); + } + + /** + * Retrieve cases hydrated from the given key or NULL + * + * @param callable|string $key + * @param mixed $value + * @return CasesCollection|static|null + */ + public static function tryFromKey(callable|string $key, mixed $value): CasesCollection|static|null + { + $cases = []; + + foreach (static::cases() as $case) { + if ($case->get($key) === $value) { + $cases[] = $case; + } + } + + return match (count($cases)) { + 0 => null, + 1 => $cases[0], + default => new CasesCollection($cases), + }; + } + + /** + * Retrieve cases hydrated from keys dynamically + * + * @param string $name + * @param array $parameters + * @return CasesCollection|static|null + */ + public static function __callStatic(string $name, array $parameters): CasesCollection|static|null + { + return match (0) { + strpos($name, 'from') => static::fromKey(lcfirst(substr($name, 4)), $parameters[0] ?? true), + strpos($name, 'tryFrom') => static::tryFromKey(lcfirst(substr($name, 7)), $parameters[0] ?? true), + }; + } +} diff --git a/src/Concerns/KeysAware.php b/src/Concerns/KeysAware.php new file mode 100644 index 0000000..73578e1 --- /dev/null +++ b/src/Concerns/KeysAware.php @@ -0,0 +1,30 @@ +$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 new file mode 100644 index 0000000..6c69832 --- /dev/null +++ b/src/Concerns/SelfAware.php @@ -0,0 +1,32 @@ + Date: Fri, 8 Jul 2022 17:31:27 +0200 Subject: [PATCH 03/21] Update readme --- README.md | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7588409..d0247a3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Enum +# ๐ŸŽฒ Enum [![Author][ico-author]][link-author] [![PHP Version][ico-php]][link-php] @@ -10,7 +10,9 @@ [![PSR-12][ico-psr12]][link-psr12] [![Total Downloads][ico-downloads]][link-downloads] -PHP library to extend enum functionalities. +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) ## ๐Ÿ“ฆ Install @@ -23,7 +25,209 @@ composer require cerbero/enum ## ๐Ÿ”ฎ Usage -// @TODO +* [Classification](#classification) +* [Comparison](#comparison) +* [Keys resolution](#keys-resolution) +* [Hydration](#hydration) +* [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: + +```php +use Cerbero\Enum\Concerns\Enumerates; + +enum PureEnum +{ + use Enumerates; + + case one; + case two; + case three; +} + +enum BackedEnum: int +{ + use Enumerates; + + case one = 1; + case two = 2; + case three = 3; +} +``` + + +### Classification + +These methods determine whether an enum is pure or backed: + +```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('four'); // false +PureEnum::doesntHave('one'); // false +PureEnum::doesntHave('four'); // true + +BackedEnum::has(1); // true +BackedEnum::has(4); // false +BackedEnum::doesntHave(1); // false +BackedEnum::doesntHave(4); // true +``` + +Otherwise we can let cases determine whether they match with a name or a 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 +``` + +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([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 + +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: + +```php +enum BackedEnum: int +{ + use Enumerates; + + case one = 1; + case two = 2; + case three = 3; + + public function color(): string + { + return match ($this) { + static::one => 'red', + static::two => 'green', + static::three => 'blue', + }; + } + + public function isOdd(): bool + { + return match ($this) { + static::one => true, + static::two => false, + static::three => true, + }; + } +} +``` + +The keys defined in this enum are `name`, `value` (as it is a backed enum), `color`, `isOdd`. We can retrieve any key assigned to a case by calling `get()`: + +```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 +``` + +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 +``` + +However `get()` is useful to resolve keys dynamically as a key may be a property, a method or a closure. It also gets called internally for more advanced functionalities that we are going to explore very soon. + + +### Hydration + +An enum case can be instantiated from its own name, value (if backed) and [keys](#keys-resolution): + +```php +PureEnum::from('one'); // PureEnum::one +PureEnum::from('four'); // throws ValueError +PureEnum::tryFrom('one'); // PureEnum::one +PureEnum::tryFrom('four'); // null +PureEnum::fromName('one'); // PureEnum::one +PureEnum::fromName('four'); // throws ValueError +PureEnum::tryFromName('one'); // PureEnum::one +PureEnum::tryFromName('four'); // null +PureEnum::fromKey('name', 'one'); // PureEnum::one +PureEnum::fromKey('value', 1); // throws ValueError +PureEnum::fromKey('color', 'red'); // PureEnum::one +PureEnum::fromKey(fn (PureEnum $case) => $case->isOdd(), true); // CasesCollection +PureEnum::tryFromKey('name', 'one'); // PureEnum::one +PureEnum::tryFromKey('value', 1); // null +PureEnum::tryFromKey('color', 'red'); // PureEnum::one +PureEnum::tryFromKey(fn (PureEnum $case) => $case->isOdd(), true); // CasesCollection +PureEnum::fromColor('red'); // PureEnum::one +PureEnum::fromInvalid('invalid'); // throws ValueError +PureEnum::fromIsOdd(); // CasesCollection +PureEnum::tryFromColor('red'); // PureEnum::one +PureEnum::tryFromInvalid('invalid'); // null +PureEnum::tryFromIsOdd(); // CasesCollection + +BackedEnum::from(1); // BackedEnum::one +BackedEnum::from('1'); // throws ValueError +BackedEnum::tryFrom(1); // BackedEnum::one +BackedEnum::tryFrom('1'); // null +BackedEnum::fromName('one'); // BackedEnum::one +BackedEnum::fromName('four'); // throws ValueError +BackedEnum::tryFromName('one'); // BackedEnum::one +BackedEnum::tryFromName('four'); // null +BackedEnum::fromKey('name', 'one'); // BackedEnum::one +BackedEnum::fromKey('value', 1); // BackedEnum::one +BackedEnum::fromKey('color', 'red'); // BackedEnum::one +BackedEnum::fromKey(fn (BackedEnum $case) => $case->isOdd(), true); // CasesCollection +BackedEnum::tryFromKey('name', 'one'); // BackedEnum::one +BackedEnum::tryFromKey('value', 1); // BackedEnum::one +BackedEnum::tryFromKey('color', 'red'); // BackedEnum::one +BackedEnum::tryFromKey(fn (BackedEnum $case) => $case->isOdd(), true); // CasesCollection +BackedEnum::fromColor('red'); // BackedEnum::one +BackedEnum::fromInvalid('invalid'); // throws ValueError +BackedEnum::fromIsOdd(); // CasesCollection +BackedEnum::tryFromColor('red'); // BackedEnum::one +BackedEnum::tryFromInvalid('invalid'); // null +BackedEnum::tryFromIsOdd(); // CasesCollection +``` ## ๐Ÿ“† Change log From a7936c4532548636835fbdb20e8b1d00bdf437d2 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 10 Jul 2022 13:03:01 +0200 Subject: [PATCH 04/21] Allow Pest plugin --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index daa24a0..4d39d58 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,9 @@ } }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true + } } } From c7a8043205adb163cd79ec262405fe9534a308a2 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 10 Jul 2022 13:03:53 +0200 Subject: [PATCH 05/21] Return cases collection when hydrating from keys --- src/Concerns/Hydrates.php | 14 +++----- tests/BackedEnumTest.php | 64 +++++++++++++++++++++++++++--------- tests/PureEnumTest.php | 68 +++++++++++++++++++++++++++------------ 3 files changed, 102 insertions(+), 44 deletions(-) diff --git a/src/Concerns/Hydrates.php b/src/Concerns/Hydrates.php index d7af5f6..0a980f7 100644 --- a/src/Concerns/Hydrates.php +++ b/src/Concerns/Hydrates.php @@ -72,10 +72,10 @@ public static function tryFromName(string $name): ?static * * @param callable|string $key * @param mixed $value - * @return CasesCollection|static + * @return CasesCollection * @throws ValueError */ - public static function fromKey(callable|string $key, mixed $value): CasesCollection|static + public static function fromKey(callable|string $key, mixed $value): CasesCollection { if ($result = static::tryFromKey($key, $value)) { return $result; @@ -91,9 +91,9 @@ public static function fromKey(callable|string $key, mixed $value): CasesCollect * * @param callable|string $key * @param mixed $value - * @return CasesCollection|static|null + * @return CasesCollection|null */ - public static function tryFromKey(callable|string $key, mixed $value): CasesCollection|static|null + public static function tryFromKey(callable|string $key, mixed $value): CasesCollection|null { $cases = []; @@ -103,11 +103,7 @@ public static function tryFromKey(callable|string $key, mixed $value): CasesColl } } - return match (count($cases)) { - 0 => null, - 1 => $cases[0], - default => new CasesCollection($cases), - }; + return $cases ? new CasesCollection($cases) : null; } /** diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index 8caf2ec..75fc1e4 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -276,34 +276,40 @@ ['four', null], ]); -it('retrieves the case hydrated from a key') - ->expect(fn (callable|string $key, mixed $value, BackedEnum $case) => BackedEnum::fromKey($key, $value) === $case) +it('retrieves the cases hydrated from a key') + ->expect(fn (string $key, mixed $value, array $cases) => BackedEnum::fromKey($key, $value)->cases() === $cases) ->toBeTrue() ->with([ - ['color', 'red', BackedEnum::one], - ['name', 'three', BackedEnum::three], + ['color', 'red', [BackedEnum::one]], + ['name', 'three', [BackedEnum::three]], + ['odd', true, [BackedEnum::one, BackedEnum::three]], ]); -it('retrieves the case hydrated from a key using a closure') +it('retrieves the cases hydrated from a key using a closure') ->expect(BackedEnum::fromKey(fn (BackedEnum $case) => $case->shape(), 'square')) - ->toBe(BackedEnum::two); + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->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 (callable|string $key, mixed $value, ?BackedEnum $case) => BackedEnum::tryFromKey($key, $value) === $case) + ->expect(fn (string $key, mixed $value, ?array $cases) => BackedEnum::tryFromKey($key, $value)?->cases() === $cases) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ - ['color', 'red', BackedEnum::one], - ['name', 'three', BackedEnum::three], + ['color', 'red', [BackedEnum::one]], + ['name', 'three', [BackedEnum::three]], + ['odd', 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')) - ->toBe(BackedEnum::two); + ->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) @@ -324,25 +330,53 @@ it('retrieves the case hydrated from a key dynamically') ->expect(BackedEnum::fromColor('red')) - ->toBe(BackedEnum::one); + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one]); -it('retrieves all cases hydrated from a key dynamically') +it('retrieves all cases hydrated from a key dynamically without value') ->expect(BackedEnum::fromOdd()) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::one, BackedEnum::three]); +it('retrieves all cases hydrated from a key dynamically') + ->expect(BackedEnum::fromOdd(false)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::two]); + it('throws a value error when hydrating cases with an invalid key dynamically', fn () => BackedEnum::fromOdd(123)) ->throws(ValueError::class, 'Invalid value for the key "odd" for enum "Cerbero\Enum\BackedEnum"'); -it('attempts to retrieve the case hydrated from a key dynamically') +it('attempts to retrieve the cases hydrated from a key dynamically') ->expect(BackedEnum::tryFromColor('red')) - ->toBe(BackedEnum::one) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one]) ->and(BackedEnum::tryFromColor('violet')) ->toBeNull(); -it('attempts to retrieve all cases hydrated from a key dynamically') +it('attempts to retrieve the cases hydrated from a key dynamically without value') ->expect(BackedEnum::tryFromOdd()) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([BackedEnum::one, BackedEnum::three]); + +it('attempts to retrieve the cases hydrated from a key dynamically with value') + ->expect(BackedEnum::tryFromOdd(false)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::two]); + +it('retrieves the first case of a collection') + ->expect(BackedEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->first() + ->toBe(BackedEnum::one); + +it('retrieves the first case of a collection based on a closure') + ->expect(BackedEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->first(fn (BackedEnum $case) => $case->name === 'three') + ->toBe(BackedEnum::three); diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index 88c2373..ffc1d17 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -276,40 +276,40 @@ ['four', null], ]); -it('retrieves the case hydrated from a key') - ->expect(fn (callable|string $key, mixed $value, PureEnum $case) => PureEnum::fromKey($key, $value) === $case) +it('retrieves the cases hydrated from a key') + ->expect(fn (string $key, mixed $value, array $cases) => PureEnum::fromKey($key, $value)->cases() === $cases) ->toBeTrue() ->with([ - ['color', 'red', PureEnum::one], - ['name', 'three', PureEnum::three], + ['color', 'red', [PureEnum::one]], + ['name', 'three', [PureEnum::three]], + ['odd', true, [PureEnum::one, PureEnum::three]], ]); -it('retrieves all cases hydrated from a key') - ->expect(PureEnum::fromKey('odd', true)) +it('retrieves the cases hydrated from a key using a closure') + ->expect(PureEnum::fromKey(fn (PureEnum $case) => $case->shape(), 'square')) ->toBeInstanceOf(CasesCollection::class) ->cases() - ->toBe([PureEnum::one, PureEnum::three]); - -it('retrieves the case hydrated from a key using a closure') - ->expect(PureEnum::fromKey(fn (PureEnum $case) => $case->shape(), 'square')) - ->toBe(PureEnum::two); + ->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 (callable|string $key, mixed $value, ?PureEnum $case) => PureEnum::tryFromKey($key, $value) === $case) + ->expect(fn (string $key, mixed $value, ?array $cases) => PureEnum::tryFromKey($key, $value)?->cases() === $cases) ->toBeTrue() ->not->toThrow(ValueError::class) ->with([ - ['color', 'red', PureEnum::one], - ['name', 'three', PureEnum::three], + ['color', 'red', [PureEnum::one]], + ['name', 'three', [PureEnum::three]], + ['odd', 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')) - ->toBe(PureEnum::two); + ->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) @@ -329,25 +329,53 @@ it('retrieves the case hydrated from a key dynamically') ->expect(PureEnum::fromColor('red')) - ->toBe(PureEnum::one); + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one]); -it('retrieves all cases hydrated from a key dynamically') +it('retrieves all cases hydrated from a key dynamically without value') ->expect(PureEnum::fromOdd()) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::three]); +it('retrieves all cases hydrated from a key dynamically') + ->expect(PureEnum::fromOdd(false)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two]); + it('throws a value error when hydrating cases with an invalid key dynamically', fn () => PureEnum::fromOdd(123)) ->throws(ValueError::class, 'Invalid value for the key "odd" for enum "Cerbero\Enum\PureEnum"'); -it('attempts to retrieve the case hydrated from a key dynamically') +it('attempts to retrieve the cases hydrated from a key dynamically') ->expect(PureEnum::tryFromColor('red')) - ->toBe(PureEnum::one) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one]) ->and(PureEnum::tryFromColor('violet')) ->toBeNull(); -it('attempts to retrieve all cases hydrated from a key dynamically') +it('attempts to retrieve the cases hydrated from a key dynamically without value') ->expect(PureEnum::tryFromOdd()) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::three]); + +it('attempts to retrieve the cases hydrated from a key dynamically with value') + ->expect(PureEnum::tryFromOdd(false)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two]); + +it('retrieves the first case of a collection') + ->expect(PureEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->first() + ->toBe(PureEnum::one); + +it('retrieves the first case of a collection based on a closure') + ->expect(PureEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->first(fn (PureEnum $case) => $case->name === 'three') + ->toBe(PureEnum::three); From e6d013835bf713847e743b2bf2f1094f3f5e4e19 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 10 Jul 2022 13:04:20 +0200 Subject: [PATCH 06/21] Retrieve the first case --- src/CasesCollection.php | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index e6aa4d8..2d7972a 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -25,7 +25,7 @@ class CasesCollection */ public function __construct(protected array $cases) { - $this->enumIsBacked = ($cases[0] ?? null) instanceof BackedEnum; + $this->enumIsBacked = $this->first() instanceof BackedEnum; } /** @@ -38,10 +38,29 @@ public function cases(): array return $this->cases; } + /** + * Retrieve the first case + * + * @param callable|null $callback + * @return mixed + */ + public function first(callable $callback = null): mixed + { + $callback ??= fn () => true; + + foreach ($this->cases as $case) { + if ($callback($case)) { + return $case; + } + } + + return null; + } + /** * Retrieve the cases keyed by name * - * @return array + * @return array */ public function keyByName(): array { @@ -52,7 +71,7 @@ public function keyByName(): array * Retrieve the cases keyed by the given key * * @param callable|string $key - * @return array + * @return array */ public function keyBy(callable|string $key): array { @@ -68,7 +87,7 @@ public function keyBy(callable|string $key): array /** * Retrieve the cases keyed by value * - * @return array + * @return array */ public function keyByValue(): array { From f8c2130b810a2943cc28043c115c8eb815ad15a5 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 10 Jul 2022 13:07:50 +0200 Subject: [PATCH 07/21] Fix PHP version for the coverage job --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5478bb..b08aacb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 extensions: dom, curl, libxml, mbstring, zip tools: composer:v2 coverage: xdebug From 134f42ec66526266bcc7d24e5e865b5a9439bca9 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Sun, 10 Jul 2022 13:14:36 +0200 Subject: [PATCH 08/21] Configure image and environment --- .scrutinizer.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 5d63b30..425b62c 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,4 +1,7 @@ build: + image: default-bionic + environment: + php: 8.1.2 nodes: analysis: project_setup: From 23c9e26a5057561238090e49009fa2f993dfeafd Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 11 Jul 2022 14:29:32 +0200 Subject: [PATCH 09/21] Create tests --- tests/CasesCollectionTest.php | 188 ++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 tests/CasesCollectionTest.php diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php new file mode 100644 index 0000000..61e7448 --- /dev/null +++ b/tests/CasesCollectionTest.php @@ -0,0 +1,188 @@ +expect(new CasesCollection(PureEnum::cases())) + ->cases() + ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); + +it('retrieves the count of all the cases') + ->expect(new CasesCollection(PureEnum::cases())) + ->count() + ->toBe(3); + +it('retrieves the first case') + ->expect(new CasesCollection(PureEnum::cases())) + ->first() + ->toBe(PureEnum::one); + +it('retrieves the first case with a closure') + ->expect(new CasesCollection(PureEnum::cases())) + ->first(fn (PureEnum $case) => !$case->odd()) + ->toBe(PureEnum::two); + +it('returns null if no case is present') + ->expect(new CasesCollection([])) + ->first() + ->toBeNull(); + +it('retrieves the cases keyed by name') + ->expect(new CasesCollection(PureEnum::cases())) + ->keyByName() + ->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') + ->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() + ->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') + ->expect(new CasesCollection(PureEnum::cases())) + ->keyByValue() + ->toBeEmpty(); + +it('retrieves all the names of the cases') + ->expect(new CasesCollection(PureEnum::cases())) + ->names() + ->toBe(['one', 'two', 'three']); + +it('retrieves all the values of the cases') + ->expect(new CasesCollection(BackedEnum::cases())) + ->values() + ->toBe([1, 2, 3]); + +it('retrieves an empty array when trying to retrieve values belonging to a pure enum') + ->expect(new CasesCollection(PureEnum::cases())) + ->values() + ->toBeEmpty(); + +it('retrieves all the values of a particular key for all cases') + ->expect(new CasesCollection(PureEnum::cases())) + ->keys('color') + ->toBe(['red', 'green', 'blue']); + +it('retrieves all the values of a particular key for all cases with a closure') + ->expect(new CasesCollection(PureEnum::cases())) + ->keys(fn (PureEnum $case) => $case->shape()) + ->toBe(['triangle', 'square', 'circle']); + +it('retrieves a list of names by default when plucking a pure enum') + ->expect(new CasesCollection(PureEnum::cases())) + ->pluck() + ->toBe(['one', 'two', 'three']); + +it('retrieves a list of names by default when plucking a backed enum') + ->expect(new CasesCollection(BackedEnum::cases())) + ->pluck() + ->toBe([1, 2, 3]); + +it('retrieves a list of custom values when plucking with an argument') + ->expect(new CasesCollection(PureEnum::cases())) + ->pluck('color') + ->toBe(['red', 'green', 'blue']); + +it('retrieves a list of custom values when plucking with a closure') + ->expect(new CasesCollection(PureEnum::cases())) + ->pluck(fn (PureEnum $case) => $case->shape()) + ->toBe(['triangle', 'square', 'circle']); + +it('retrieves an associative array with custom values and keys when plucking with arguments') + ->expect(new CasesCollection(PureEnum::cases())) + ->pluck('shape', 'color') + ->toBe(['red' => 'triangle', 'green' => 'square', 'blue' => 'circle']); + +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()) + ->toBe(['red' => 'triangle', 'green' => 'square', 'blue' => 'circle']); + +it('retrieves a collection with filtered cases') + ->expect((new CasesCollection(PureEnum::cases()))->filter(fn (PureEnum $case) => $case->odd())) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::three]); + +it('retrieves a collection of cases with the given names') + ->expect((new CasesCollection(PureEnum::cases()))->only('one', 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::three]); + +it('retrieves a collection of cases excluding the given names') + ->expect((new CasesCollection(PureEnum::cases()))->except('one', 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two]); + +it('retrieves a collection of cases with the given values') + ->expect((new CasesCollection(BackedEnum::cases()))->onlyValues(1, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::three]); + +it('retrieves a collection of cases excluding the given values') + ->expect((new CasesCollection(BackedEnum::cases()))->exceptValues(1, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::two]); + +it('retrieves an empty collection of cases when when including values of pure enums') + ->expect((new CasesCollection(PureEnum::cases()))->onlyValues(1, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); + +it('retrieves an empty collection of cases when when excluding values of pure enums') + ->expect((new CasesCollection(PureEnum::cases()))->exceptValues(1, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); + +it('retrieves a collection of cases sorted by name ascending') + ->expect((new CasesCollection(PureEnum::cases()))->sort()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::three, PureEnum::two]); + +it('retrieves a collection of cases sorted by name decending') + ->expect((new CasesCollection(PureEnum::cases()))->sortDesc()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two, PureEnum::three, PureEnum::one]); + +it('retrieves a collection of cases sorted by a key ascending') + ->expect((new CasesCollection(PureEnum::cases()))->sortBy('color')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::three, PureEnum::two, PureEnum::one]); + +it('retrieves a collection of cases sorted by a key decending') + ->expect((new CasesCollection(PureEnum::cases()))->sortDescBy('color')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); + +it('retrieves a collection of cases sorted by value ascending') + ->expect((new CasesCollection(BackedEnum::cases()))->sortByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); + +it('retrieves a collection of cases sorted by value decending') + ->expect((new CasesCollection(BackedEnum::cases()))->sortDescByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); From cf41216eda1bcb3cf9bb4a40f6123c24c803d842 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 11 Jul 2022 14:29:46 +0200 Subject: [PATCH 10/21] Compare values strictly --- src/CasesCollection.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 2d7972a..f939cdd 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -38,6 +38,16 @@ public function cases(): array return $this->cases; } + /** + * Retrieve the count of cases + * + * @return int + */ + public function count(): int + { + return count($this->cases); + } + /** * Retrieve the first case * @@ -191,7 +201,7 @@ public function except(string ...$name): static */ public function onlyValues(string|int ...$value): static { - return $this->filter(fn (UnitEnum $enum) => $this->enumIsBacked && in_array($enum->value, $value)); + return $this->filter(fn (UnitEnum $enum) => $this->enumIsBacked && in_array($enum->value, $value, true)); } /** @@ -202,7 +212,7 @@ public function onlyValues(string|int ...$value): static */ public function exceptValues(string|int ...$value): static { - return $this->filter(fn (UnitEnum $enum) => $this->enumIsBacked && !in_array($enum->value, $value)); + return $this->filter(fn (UnitEnum $enum) => $this->enumIsBacked && !in_array($enum->value, $value, true)); } /** @@ -274,14 +284,4 @@ public function sortDescByValue(): static { return $this->enumIsBacked ? $this->sortDescBy('value') : new static([]); } - - /** - * Retrieve the count of cases - * - * @return int - */ - public function count(): int - { - return count($this->cases); - } } From 051e8a8804f32702ea33ab6fbaa6acafc028de57 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 11 Jul 2022 14:30:28 +0200 Subject: [PATCH 11/21] Test keys retrieval with closures --- tests/BackedEnumTest.php | 4 ++++ tests/PureEnumTest.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index 75fc1e4..75ce3b9 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -23,6 +23,10 @@ ->expect(BackedEnum::keys('color')) ->toBe(['red', 'green', 'blue']); +it('retrieves all the keys of the cases with a closure') + ->expect(BackedEnum::keys(fn (BackedEnum $case) => $case->shape())) + ->toBe(['triangle', 'square', 'circle']); + it('throws a value error when requesting an invalid key', fn () => BackedEnum::keys('invalid')) ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\BackedEnum"'); diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index ffc1d17..f29087c 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -23,6 +23,10 @@ ->expect(PureEnum::keys('color')) ->toBe(['red', 'green', 'blue']); +it('retrieves all the keys of the cases with a closure') + ->expect(PureEnum::keys(fn (PureEnum $case) => $case->shape())) + ->toBe(['triangle', 'square', 'circle']); + it('throws a value error when requesting an invalid key', fn () => PureEnum::keys('invalid')) ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); From 0dae79922b080b7a1895514b56bda5004dae71fd Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 11 Jul 2022 15:51:27 +0200 Subject: [PATCH 12/21] Rename variable --- src/CasesCollection.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index f939cdd..e060539 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -179,7 +179,7 @@ public function filter(callable $callback): static */ public function only(string ...$name): static { - return $this->filter(fn (UnitEnum $enum) => in_array($enum->name, $name)); + return $this->filter(fn (UnitEnum $case) => in_array($case->name, $name)); } /** @@ -190,7 +190,7 @@ public function only(string ...$name): static */ public function except(string ...$name): static { - return $this->filter(fn (UnitEnum $enum) => !in_array($enum->name, $name)); + return $this->filter(fn (UnitEnum $case) => !in_array($case->name, $name)); } /** @@ -201,7 +201,7 @@ public function except(string ...$name): static */ public function onlyValues(string|int ...$value): static { - return $this->filter(fn (UnitEnum $enum) => $this->enumIsBacked && in_array($enum->value, $value, true)); + return $this->filter(fn (UnitEnum $case) => $this->enumIsBacked && in_array($case->value, $value, true)); } /** @@ -212,7 +212,7 @@ public function onlyValues(string|int ...$value): static */ public function exceptValues(string|int ...$value): static { - return $this->filter(fn (UnitEnum $enum) => $this->enumIsBacked && !in_array($enum->value, $value, true)); + return $this->filter(fn (UnitEnum $case) => $this->enumIsBacked && !in_array($case->value, $value, true)); } /** From 686b42dcf4c31d702813d15f4bfbec28ba709d4b Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 11 Jul 2022 15:51:35 +0200 Subject: [PATCH 13/21] Improve tests --- tests/CasesCollectionTest.php | 167 +++++++++-------- tests/PureEnumTest.php | 330 ++++++++++++++++++---------------- 2 files changed, 267 insertions(+), 230 deletions(-) diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php index 61e7448..8c188ff 100644 --- a/tests/CasesCollectionTest.php +++ b/tests/CasesCollectionTest.php @@ -109,80 +109,93 @@ ->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') - ->expect((new CasesCollection(PureEnum::cases()))->filter(fn (PureEnum $case) => $case->odd())) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); - -it('retrieves a collection of cases with the given names') - ->expect((new CasesCollection(PureEnum::cases()))->only('one', 'three')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); - -it('retrieves a collection of cases excluding the given names') - ->expect((new CasesCollection(PureEnum::cases()))->except('one', 'three')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two]); - -it('retrieves a collection of cases with the given values') - ->expect((new CasesCollection(BackedEnum::cases()))->onlyValues(1, 3)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one, BackedEnum::three]); - -it('retrieves a collection of cases excluding the given values') - ->expect((new CasesCollection(BackedEnum::cases()))->exceptValues(1, 3)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two]); - -it('retrieves an empty collection of cases when when including values of pure enums') - ->expect((new CasesCollection(PureEnum::cases()))->onlyValues(1, 3)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBeEmpty(); - -it('retrieves an empty collection of cases when when excluding values of pure enums') - ->expect((new CasesCollection(PureEnum::cases()))->exceptValues(1, 3)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBeEmpty(); - -it('retrieves a collection of cases sorted by name ascending') - ->expect((new CasesCollection(PureEnum::cases()))->sort()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three, PureEnum::two]); - -it('retrieves a collection of cases sorted by name decending') - ->expect((new CasesCollection(PureEnum::cases()))->sortDesc()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two, PureEnum::three, PureEnum::one]); - -it('retrieves a collection of cases sorted by a key ascending') - ->expect((new CasesCollection(PureEnum::cases()))->sortBy('color')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::three, PureEnum::two, PureEnum::one]); - -it('retrieves a collection of cases sorted by a key decending') - ->expect((new CasesCollection(PureEnum::cases()))->sortDescBy('color')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); - -it('retrieves a collection of cases sorted by value ascending') - ->expect((new CasesCollection(BackedEnum::cases()))->sortByValue()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); - -it('retrieves a collection of cases sorted by value decending') - ->expect((new CasesCollection(BackedEnum::cases()))->sortDescByValue()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); +it('retrieves a collection with filtered cases', function () { + expect((new CasesCollection(PureEnum::cases()))->filter(fn (PureEnum $case) => $case->odd())) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, 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]); +}); + +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]); +}); + +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]); +}); + +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]); +}); + +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() + ->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() + ->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]); +}); + +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]); +}); + +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]); +}); + +it('retrieves a collection of cases sorted by a key decending', function () { + expect((new CasesCollection(PureEnum::cases()))->sortDescBy('color')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->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() + ->toBe([BackedEnum::one, BackedEnum::two, BackedEnum::three]); +}); + +it('retrieves a collection of cases sorted by value decending', function () { + expect((new CasesCollection(BackedEnum::cases()))->sortDescByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); +}); diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index f29087c..3875c0b 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -11,92 +11,103 @@ ->expect(PureEnum::isBacked()) ->toBeFalse(); -it('retrieves all the names of the cases') - ->expect(PureEnum::names()) - ->toBe(['one', 'two', 'three']); - -it('retrieves all the values of the backed cases') - ->expect(PureEnum::values()) - ->toBeEmpty(); - -it('retrieves all the keys of the cases') - ->expect(PureEnum::keys('color')) - ->toBe(['red', 'green', 'blue']); - -it('retrieves all the keys of the cases with a closure') - ->expect(PureEnum::keys(fn (PureEnum $case) => $case->shape())) - ->toBe(['triangle', 'square', 'circle']); - -it('throws a value error when requesting an invalid key', fn () => PureEnum::keys('invalid')) - ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); - it('retrieves a collection with all the cases') ->expect(PureEnum::collect()) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); -it('retrieves all cases keyed by name') - ->expect(PureEnum::casesByName()) - ->toBe(['one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three]); - -it('retrieves all cases keyed by value') - ->expect(PureEnum::casesByValue()) - ->toBeEmpty(); - -it('retrieves all cases keyed by a custom key') - ->expect(PureEnum::casesBy('color')) - ->toBe(['red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three]); - -it('retrieves all cases keyed by the result of a closure') - ->expect(PureEnum::casesBy(fn (PureEnum $case) => $case->shape())) - ->toBe(['triangle' => PureEnum::one, 'square' => PureEnum::two, 'circle' => PureEnum::three]); - -it('retrieves a collection with the filtered cases') - ->expect(PureEnum::filter(fn (UnitEnum $case) => $case->name !== 'three')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::two]); +it('retrieves all cases keyed by name', function () { + expect(PureEnum::casesByName()) + ->toBe(['one' => PureEnum::one, 'two' => PureEnum::two, 'three' => PureEnum::three]); +}); -it('retrieves a collection of cases having the given names') - ->expect(PureEnum::only('two', 'three')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two, PureEnum::three]); +it('retrieves all cases keyed by value', function () { + expect(PureEnum::casesByValue()) + ->toBeEmpty(); +}); -it('retrieves a collection of cases not having the given names') - ->expect(PureEnum::except('one', 'three')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two]); +it('retrieves all cases keyed by a custom key', function () { + expect(PureEnum::casesBy('color')) + ->toBe(['red' => PureEnum::one, 'green' => PureEnum::two, 'blue' => PureEnum::three]); +}); -it('retrieves a collection of backed cases having the given values') - ->expect(PureEnum::onlyValues(2, 3)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBeEmpty(); +it('retrieves all cases keyed by the result of a closure', function () { + expect(PureEnum::casesBy(fn (PureEnum $case) => $case->shape())) + ->toBe(['triangle' => PureEnum::one, 'square' => PureEnum::two, 'circle' => PureEnum::three]); +}); -it('retrieves a collection of backed cases not having the given values') - ->expect(PureEnum::exceptValues(1, 3)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBeEmpty(); +it('retrieves all the names of the cases', function () { + expect(PureEnum::names())->toBe(['one', 'two', 'three']); +}); -it('retrieves an array of values') - ->expect(PureEnum::pluck()) - ->toBe(['one', 'two', 'three']); +it('retrieves all the values of the backed cases', function () { + expect(PureEnum::values())->toBeEmpty(); +}); -it('retrieves an array of custom values') - ->expect(PureEnum::pluck('color')) +it('retrieves all the keys of the cases') + ->expect(PureEnum::keys('color')) ->toBe(['red', 'green', 'blue']); -it('retrieves an associative array with custom keys and values') - ->expect(PureEnum::pluck('color', 'shape')) - ->toBe(['triangle' => 'red', 'square' => 'green', 'circle' => 'blue']); +it('retrieves all the keys of the cases with a closure') + ->expect(PureEnum::keys(fn (PureEnum $case) => $case->shape())) + ->toBe(['triangle', 'square', 'circle']); -it('retrieves an associative array with keys and values resolved from closures') - ->expect(PureEnum::pluck(fn (PureEnum $case) => $case->name, fn (PureEnum $case) => $case->color())) - ->toBe(['red' => 'one', 'green' => 'two', 'blue' => 'three']); +it('throws a value error when requesting an invalid key', fn () => PureEnum::keys('invalid')) + ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); + +it('retrieves a collection with the filtered cases', function () { + expect(PureEnum::filter(fn (UnitEnum $case) => $case->name !== 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::two]); +}); + +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]); +}); + +it('retrieves a collection of cases not having the given names', function () { + expect(PureEnum::except('one', 'three')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two]); +}); + +it('retrieves a collection of backed cases having the given values', function () { + expect(PureEnum::onlyValues(2, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); +}); + +it('retrieves a collection of backed cases not having the given values', function () { + expect(PureEnum::exceptValues(1, 3)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); +}); + +it('retrieves an array of values', function () { + expect(PureEnum::pluck())->toBe(['one', 'two', 'three']); +}); + +it('retrieves an array of custom values', function () { + expect(PureEnum::pluck('color'))->toBe(['red', 'green', 'blue']); +}); + +it('retrieves an associative array with custom keys and values', function () { + expect(PureEnum::pluck('color', 'shape')) + ->toBe(['triangle' => 'red', 'square' => 'green', 'circle' => 'blue']); +}); + +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())) + ->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) @@ -182,57 +193,65 @@ [['2', '3'], true], ]); -it('retrieves a collection of cases sorted by name ascending') - ->expect(PureEnum::sort()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three, PureEnum::two]); - -it('retrieves a collection of cases sorted by name descending') - ->expect(PureEnum::sortDesc()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two, PureEnum::three, PureEnum::one]); - -it('retrieves a collection of cases sorted by value ascending') - ->expect(PureEnum::sortByValue()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBeEmpty(); - -it('retrieves a collection of cases sorted by value descending') - ->expect(PureEnum::sortDescByValue()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBeEmpty(); - -it('retrieves a collection of cases sorted by a custom value ascending') - ->expect(PureEnum::sortBy('color')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::three, PureEnum::two, PureEnum::one]); - -it('retrieves a collection of cases sorted by a custom value descending') - ->expect(PureEnum::sortDescBy('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') - ->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') - ->expect(PureEnum::sortDescBy(fn (PureEnum $case) => $case->shape())) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); - -it('retrieves the count of cases') - ->expect(PureEnum::count()) - ->toBe(3); +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]); +}); + +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]); +}); + +it('retrieves a collection of cases sorted by value ascending', function () { + expect(PureEnum::sortByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBeEmpty(); +}); + +it('retrieves a collection of cases sorted by value descending', function () { + expect(PureEnum::sortDescByValue()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->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]); +}); + +it('retrieves a collection of cases sorted by a custom value descending', function () { + expect(PureEnum::sortDescBy('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())) + ->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())) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::two, PureEnum::three]); +}); + +it('retrieves the count of cases', function () { + expect(PureEnum::count())->toBe(3); +}); it('retrieves the case hydrated from a value') ->expect(fn (string $value, PureEnum $case) => PureEnum::from($value) === $case) @@ -352,34 +371,39 @@ it('throws a value error when hydrating cases with an invalid key dynamically', fn () => PureEnum::fromOdd(123)) ->throws(ValueError::class, 'Invalid value for the key "odd" for enum "Cerbero\Enum\PureEnum"'); -it('attempts to retrieve the cases hydrated from a key dynamically') - ->expect(PureEnum::tryFromColor('red')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one]) - ->and(PureEnum::tryFromColor('violet')) - ->toBeNull(); - -it('attempts to retrieve the cases hydrated from a key dynamically without value') - ->expect(PureEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); - -it('attempts to retrieve the cases hydrated from a key dynamically with value') - ->expect(PureEnum::tryFromOdd(false)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two]); - -it('retrieves the first case of a collection') - ->expect(PureEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->first() - ->toBe(PureEnum::one); - -it('retrieves the first case of a collection based on a closure') - ->expect(PureEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->first(fn (PureEnum $case) => $case->name === 'three') - ->toBe(PureEnum::three); +it('attempts to retrieve the cases hydrated from a key dynamically', function () { + expect(PureEnum::tryFromColor('red')) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one]) + ->and(PureEnum::tryFromColor('violet')) + ->toBeNull(); +}); + +it('attempts to retrieve the cases hydrated from a key dynamically without value', function () { + expect(PureEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::one, PureEnum::three]); +}); + +it('attempts to retrieve the cases hydrated from a key dynamically with value', function () { + expect(PureEnum::tryFromOdd(false)) + ->toBeInstanceOf(CasesCollection::class) + ->cases() + ->toBe([PureEnum::two]); +}); + +it('retrieves the first case of a collection', function () { + expect(PureEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->first() + ->toBe(PureEnum::one); +}); + +it('retrieves the first case of a collection based on a closure', function () { + expect(PureEnum::tryFromOdd()) + ->toBeInstanceOf(CasesCollection::class) + ->first(fn (PureEnum $case) => $case->name === 'three') + ->toBe(PureEnum::three); +}); From 73fc5c0dddc32bb6c5423a280ad867176b93c93e Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 11 Jul 2022 16:38:28 +0200 Subject: [PATCH 14/21] Remove dynamic keys hydration --- src/Concerns/Hydrates.php | 15 ---------- tests/BackedEnumTest.php | 53 ----------------------------------- tests/PureEnumTest.php | 58 --------------------------------------- 3 files changed, 126 deletions(-) diff --git a/src/Concerns/Hydrates.php b/src/Concerns/Hydrates.php index 0a980f7..2c664da 100644 --- a/src/Concerns/Hydrates.php +++ b/src/Concerns/Hydrates.php @@ -105,19 +105,4 @@ public static function tryFromKey(callable|string $key, mixed $value): CasesColl return $cases ? new CasesCollection($cases) : null; } - - /** - * Retrieve cases hydrated from keys dynamically - * - * @param string $name - * @param array $parameters - * @return CasesCollection|static|null - */ - public static function __callStatic(string $name, array $parameters): CasesCollection|static|null - { - return match (0) { - strpos($name, 'from') => static::fromKey(lcfirst(substr($name, 4)), $parameters[0] ?? true), - strpos($name, 'tryFrom') => static::tryFromKey(lcfirst(substr($name, 7)), $parameters[0] ?? true), - }; - } } diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index 75ce3b9..8664c9d 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -331,56 +331,3 @@ 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"'); - -it('retrieves the case hydrated from a key dynamically') - ->expect(BackedEnum::fromColor('red')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one]); - -it('retrieves all cases hydrated from a key dynamically without value') - ->expect(BackedEnum::fromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one, BackedEnum::three]); - -it('retrieves all cases hydrated from a key dynamically') - ->expect(BackedEnum::fromOdd(false)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two]); - -it('throws a value error when hydrating cases with an invalid key dynamically', fn () => BackedEnum::fromOdd(123)) - ->throws(ValueError::class, 'Invalid value for the key "odd" for enum "Cerbero\Enum\BackedEnum"'); - -it('attempts to retrieve the cases hydrated from a key dynamically') - ->expect(BackedEnum::tryFromColor('red')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one]) - ->and(BackedEnum::tryFromColor('violet')) - ->toBeNull(); - -it('attempts to retrieve the cases hydrated from a key dynamically without value') - ->expect(BackedEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::one, BackedEnum::three]); - -it('attempts to retrieve the cases hydrated from a key dynamically with value') - ->expect(BackedEnum::tryFromOdd(false)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([BackedEnum::two]); - -it('retrieves the first case of a collection') - ->expect(BackedEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->first() - ->toBe(BackedEnum::one); - -it('retrieves the first case of a collection based on a closure') - ->expect(BackedEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->first(fn (BackedEnum $case) => $case->name === 'three') - ->toBe(BackedEnum::three); diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index 3875c0b..bc179e4 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -349,61 +349,3 @@ 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"'); - -it('retrieves the case hydrated from a key dynamically') - ->expect(PureEnum::fromColor('red')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one]); - -it('retrieves all cases hydrated from a key dynamically without value') - ->expect(PureEnum::fromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); - -it('retrieves all cases hydrated from a key dynamically') - ->expect(PureEnum::fromOdd(false)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two]); - -it('throws a value error when hydrating cases with an invalid key dynamically', fn () => PureEnum::fromOdd(123)) - ->throws(ValueError::class, 'Invalid value for the key "odd" for enum "Cerbero\Enum\PureEnum"'); - -it('attempts to retrieve the cases hydrated from a key dynamically', function () { - expect(PureEnum::tryFromColor('red')) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one]) - ->and(PureEnum::tryFromColor('violet')) - ->toBeNull(); -}); - -it('attempts to retrieve the cases hydrated from a key dynamically without value', function () { - expect(PureEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::one, PureEnum::three]); -}); - -it('attempts to retrieve the cases hydrated from a key dynamically with value', function () { - expect(PureEnum::tryFromOdd(false)) - ->toBeInstanceOf(CasesCollection::class) - ->cases() - ->toBe([PureEnum::two]); -}); - -it('retrieves the first case of a collection', function () { - expect(PureEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->first() - ->toBe(PureEnum::one); -}); - -it('retrieves the first case of a collection based on a closure', function () { - expect(PureEnum::tryFromOdd()) - ->toBeInstanceOf(CasesCollection::class) - ->first(fn (PureEnum $case) => $case->name === 'three') - ->toBe(PureEnum::three); -}); From e45c7796c2cae3de8a3df80099136d85fb615b7f Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 11 Jul 2022 16:39:00 +0200 Subject: [PATCH 15/21] Make collection countable and iterable --- src/CasesCollection.php | 19 ++++++++++++++++++- tests/CasesCollectionTest.php | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index e060539..10cfda6 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -3,13 +3,16 @@ namespace Cerbero\Enum; use BackedEnum; +use Countable; +use IteratorAggregate; +use Traversable; use UnitEnum; /** * The collection of enum cases. * */ -class CasesCollection +class CasesCollection implements Countable, IteratorAggregate { /** * Whether the cases belong to a backed enum @@ -28,6 +31,20 @@ public function __construct(protected array $cases) $this->enumIsBacked = $this->first() instanceof BackedEnum; } + /** + * Retrieve the iterable cases + * + * @return Traversable + */ + public function getIterator(): Traversable + { + return (function () { + foreach ($this->cases as $case) { + yield $case; + } + })(); + } + /** * Retrieve the cases * diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php index 8c188ff..931914b 100644 --- a/tests/CasesCollectionTest.php +++ b/tests/CasesCollectionTest.php @@ -199,3 +199,18 @@ ->cases() ->toBe([BackedEnum::three, BackedEnum::two, BackedEnum::one]); }); + +it('retrieves the iterator', function () { + expect((new CasesCollection(PureEnum::cases()))->getIterator()) + ->toBeInstanceOf(Traversable::class); +}); + +it('iterates cases within a loop', function () { + $i = 0; + $collection = new CasesCollection(PureEnum::cases()); + $expected = [PureEnum::one, PureEnum::two, PureEnum::three]; + + foreach ($collection as $case) { + expect($case)->toBe($expected[$i++]); + } +}); From 51377dad3b40740445b9a1a58f1d9c27818f99b2 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 12 Jul 2022 09:18:24 +0200 Subject: [PATCH 16/21] Implement method to group cases by a given key --- src/CasesCollection.php | 22 +++++++++++++++-- src/Concerns/CollectsCases.php | 43 +++++++++++++++++++++------------- tests/BackedEnumTest.php | 14 +++++++++-- tests/CasesCollectionTest.php | 19 +++++++++++++-- tests/PureEnumTest.php | 14 +++++++++-- 5 files changed, 88 insertions(+), 24 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 10cfda6..26a260f 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -69,9 +69,10 @@ public function count(): int * Retrieve the first case * * @param callable|null $callback + * @param mixed $default * @return mixed */ - public function first(callable $callback = null): mixed + public function first(callable $callback = null, mixed $default = null): mixed { $callback ??= fn () => true; @@ -81,7 +82,7 @@ public function first(callable $callback = null): mixed } } - return null; + return $default; } /** @@ -121,6 +122,23 @@ public function keyByValue(): array return $this->enumIsBacked ? $this->keyBy('value') : []; } + /** + * Retrieve the cases grouped by the given key + * + * @param callable|string $key + * @return array + */ + public function groupBy(callable|string $key): array + { + $result = []; + + foreach ($this->cases as $case) { + $result[$case->get($key)][] = $case; + } + + return $result; + } + /** * Retrieve all the names of the cases * diff --git a/src/Concerns/CollectsCases.php b/src/Concerns/CollectsCases.php index 6913366..3c1a1ae 100644 --- a/src/Concerns/CollectsCases.php +++ b/src/Concerns/CollectsCases.php @@ -20,10 +20,20 @@ public static function collect(): CasesCollection return new CasesCollection(static::cases()); } + /** + * Retrieve the count of cases + * + * @return int + */ + public static function count(): int + { + return static::collect()->count(); + } + /** * Retrieve all cases keyed by name * - * @return array + * @return array */ public static function casesByName(): array { @@ -33,7 +43,7 @@ public static function casesByName(): array /** * Retrieve all cases keyed by value * - * @return array + * @return array */ public static function casesByValue(): array { @@ -44,7 +54,7 @@ public static function casesByValue(): array * Retrieve all cases keyed by the given key * * @param callable|string $key - * @return array + * @return array */ public static function casesBy(callable|string $key): array { @@ -52,10 +62,21 @@ public static function casesBy(callable|string $key): array } /** - * Retrieve all the names of the cases + * Retrieve all cases grouped by the given key * + * @param callable|string $key * @return array */ + public static function groupBy(callable|string $key): array + { + return static::collect()->groupBy($key); + } + + /** + * Retrieve all the names of the cases + * + * @return array + */ public static function names(): array { return static::collect()->names(); @@ -64,7 +85,7 @@ public static function names(): array /** * Retrieve all the values of the backed cases * - * @return array + * @return array */ public static function values(): array { @@ -75,7 +96,7 @@ public static function values(): array * Retrieve all the keys of the backed cases * * @param callable|string $key - * @return array + * @return array */ public static function keys(callable|string $key): array { @@ -210,14 +231,4 @@ public static function sortDescBy(callable|string $key): CasesCollection { return static::collect()->sortDescBy($key); } - - /** - * Retrieve the count of cases - * - * @return int - */ - public static function count(): int - { - return static::collect()->count(); - } } diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index 8664c9d..e2e9e5e 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -52,6 +52,16 @@ ->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 () { + expect(BackedEnum::groupBy('color')) + ->toBe(['red' => [BackedEnum::one], 'green' => [BackedEnum::two], 'blue' => [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]]); +}); + it('retrieves a collection with the filtered cases') ->expect(BackedEnum::filter(fn (UnitEnum $case) => $case->name !== 'three')) ->toBeInstanceOf(CasesCollection::class) @@ -286,7 +296,7 @@ ->with([ ['color', 'red', [BackedEnum::one]], ['name', 'three', [BackedEnum::three]], - ['odd', true, [BackedEnum::one, BackedEnum::three]], + ['isOdd', true, [BackedEnum::one, BackedEnum::three]], ]); it('retrieves the cases hydrated from a key using a closure') @@ -305,7 +315,7 @@ ->with([ ['color', 'red', [BackedEnum::one]], ['name', 'three', [BackedEnum::three]], - ['odd', true, [BackedEnum::one, BackedEnum::three]], + ['isOdd', true, [BackedEnum::one, BackedEnum::three]], ['shape', 'rectangle', null], ]); diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php index 931914b..6591da3 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->odd()) + ->first(fn (PureEnum $case) => !$case->isOdd()) ->toBe(PureEnum::two); it('returns null if no case is present') @@ -29,6 +29,11 @@ ->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() @@ -54,6 +59,16 @@ ->keyByValue() ->toBeEmpty(); +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]]); + +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]]); + it('retrieves all the names of the cases') ->expect(new CasesCollection(PureEnum::cases())) ->names() @@ -110,7 +125,7 @@ ->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->odd())) + expect((new CasesCollection(PureEnum::cases()))->filter(fn (PureEnum $case) => $case->isOdd())) ->toBeInstanceOf(CasesCollection::class) ->cases() ->toBe([PureEnum::one, PureEnum::three]); diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index bc179e4..a3c6846 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -37,6 +37,16 @@ ->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]]); +}); + +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]]); +}); + it('retrieves all the names of the cases', function () { expect(PureEnum::names())->toBe(['one', 'two', 'three']); }); @@ -305,7 +315,7 @@ ->with([ ['color', 'red', [PureEnum::one]], ['name', 'three', [PureEnum::three]], - ['odd', true, [PureEnum::one, PureEnum::three]], + ['isOdd', true, [PureEnum::one, PureEnum::three]], ]); it('retrieves the cases hydrated from a key using a closure') @@ -324,7 +334,7 @@ ->with([ ['color', 'red', [PureEnum::one]], ['name', 'three', [PureEnum::three]], - ['odd', true, [PureEnum::one, PureEnum::three]], + ['isOdd', true, [PureEnum::one, PureEnum::three]], ['shape', 'rectangle', null], ]); From b1688c21dd9d2850590382676a78f5a853d4270d Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 12 Jul 2022 09:19:00 +0200 Subject: [PATCH 17/21] Rename odd method --- tests/BackedEnum.php | 2 +- tests/PureEnum.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/BackedEnum.php b/tests/BackedEnum.php index 5fb49de..2c224b6 100644 --- a/tests/BackedEnum.php +++ b/tests/BackedEnum.php @@ -49,7 +49,7 @@ public function shape(): string * * @return bool */ - public function odd(): bool + public function isOdd(): bool { return match ($this) { static::one => true, diff --git a/tests/PureEnum.php b/tests/PureEnum.php index fd05bb3..648c7ce 100644 --- a/tests/PureEnum.php +++ b/tests/PureEnum.php @@ -49,7 +49,7 @@ public function shape(): string * * @return bool */ - public function odd(): bool + public function isOdd(): bool { return match ($this) { static::one => true, From 41eea85ee56beb38e371b409b806422a7c1c693f Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 12 Jul 2022 10:20:59 +0200 Subject: [PATCH 18/21] Remove the method keys to favor pluck --- src/CasesCollection.php | 11 ----------- src/Concerns/CollectsCases.php | 11 ----------- tests/BackedEnumTest.php | 11 ----------- tests/CasesCollectionTest.php | 10 ---------- tests/PureEnumTest.php | 11 ----------- 5 files changed, 54 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index 26a260f..e9c1d51 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -159,17 +159,6 @@ public function values(): array return array_column($this->cases, 'value'); } - /** - * Retrieve all the keys of the cases - * - * @param callable|string $key - * @return array - */ - public function keys(callable|string $key): array - { - return $this->pluck($key); - } - /** * Retrieve an array of values optionally keyed by the given key * diff --git a/src/Concerns/CollectsCases.php b/src/Concerns/CollectsCases.php index 3c1a1ae..2527d2d 100644 --- a/src/Concerns/CollectsCases.php +++ b/src/Concerns/CollectsCases.php @@ -92,17 +92,6 @@ public static function values(): array return static::collect()->values(); } - /** - * Retrieve all the keys of the backed cases - * - * @param callable|string $key - * @return array - */ - public static function keys(callable|string $key): array - { - return static::collect()->keys($key); - } - /** * Retrieve a collection with the filtered cases * diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index e2e9e5e..6504a80 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -19,17 +19,6 @@ ->expect(BackedEnum::values()) ->toBe([1, 2, 3]); -it('retrieves all the keys of the cases') - ->expect(BackedEnum::keys('color')) - ->toBe(['red', 'green', 'blue']); - -it('retrieves all the keys of the cases with a closure') - ->expect(BackedEnum::keys(fn (BackedEnum $case) => $case->shape())) - ->toBe(['triangle', 'square', 'circle']); - -it('throws a value error when requesting an invalid key', fn () => BackedEnum::keys('invalid')) - ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\BackedEnum"'); - it('retrieves a collection with all the cases') ->expect(BackedEnum::collect()) ->toBeInstanceOf(CasesCollection::class) diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php index 6591da3..2418606 100644 --- a/tests/CasesCollectionTest.php +++ b/tests/CasesCollectionTest.php @@ -84,16 +84,6 @@ ->values() ->toBeEmpty(); -it('retrieves all the values of a particular key for all cases') - ->expect(new CasesCollection(PureEnum::cases())) - ->keys('color') - ->toBe(['red', 'green', 'blue']); - -it('retrieves all the values of a particular key for all cases with a closure') - ->expect(new CasesCollection(PureEnum::cases())) - ->keys(fn (PureEnum $case) => $case->shape()) - ->toBe(['triangle', 'square', 'circle']); - it('retrieves a list of names by default when plucking a pure enum') ->expect(new CasesCollection(PureEnum::cases())) ->pluck() diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index a3c6846..c0672b3 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -55,17 +55,6 @@ expect(PureEnum::values())->toBeEmpty(); }); -it('retrieves all the keys of the cases') - ->expect(PureEnum::keys('color')) - ->toBe(['red', 'green', 'blue']); - -it('retrieves all the keys of the cases with a closure') - ->expect(PureEnum::keys(fn (PureEnum $case) => $case->shape())) - ->toBe(['triangle', 'square', 'circle']); - -it('throws a value error when requesting an invalid key', fn () => PureEnum::keys('invalid')) - ->throws(ValueError::class, '"invalid" is not a valid key for enum "Cerbero\Enum\PureEnum"'); - it('retrieves a collection with the filtered cases', function () { expect(PureEnum::filter(fn (UnitEnum $case) => $case->name !== 'three')) ->toBeInstanceOf(CasesCollection::class) From e9c25aeef144c65199b3e3e5c8ae3979179e706d Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 12 Jul 2022 14:12:53 +0200 Subject: [PATCH 19/21] Allow filtering cases by key --- src/CasesCollection.php | 5 +++-- src/Concerns/CollectsCases.php | 6 +++--- tests/BackedEnumTest.php | 7 +++++++ tests/CasesCollectionTest.php | 7 +++++++ tests/PureEnumTest.php | 7 +++++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/CasesCollection.php b/src/CasesCollection.php index e9c1d51..452f76e 100644 --- a/src/CasesCollection.php +++ b/src/CasesCollection.php @@ -185,11 +185,12 @@ public function pluck(callable|string $value = null, callable|string $key = null /** * Retrieve a collection with the filtered cases * - * @param callable $callback + * @param callable|string $filter * @return static */ - public function filter(callable $callback): static + public function filter(callable|string $filter): static { + $callback = is_callable($filter) ? $filter : fn (mixed $case) => $case->get($filter) === true; $cases = array_filter($this->cases, $callback); return new static(array_values($cases)); diff --git a/src/Concerns/CollectsCases.php b/src/Concerns/CollectsCases.php index 2527d2d..de50bd0 100644 --- a/src/Concerns/CollectsCases.php +++ b/src/Concerns/CollectsCases.php @@ -95,12 +95,12 @@ public static function values(): array /** * Retrieve a collection with the filtered cases * - * @param callable $callback + * @param callable|string $filter * @return CasesCollection */ - public static function filter(callable $callback): CasesCollection + public static function filter(callable|string $filter): CasesCollection { - return static::collect()->filter($callback); + return static::collect()->filter($filter); } /** diff --git a/tests/BackedEnumTest.php b/tests/BackedEnumTest.php index 6504a80..33d2772 100644 --- a/tests/BackedEnumTest.php +++ b/tests/BackedEnumTest.php @@ -57,6 +57,13 @@ ->cases() ->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]); +}); + it('retrieves a collection of cases having the given names') ->expect(BackedEnum::only('two', 'three')) ->toBeInstanceOf(CasesCollection::class) diff --git a/tests/CasesCollectionTest.php b/tests/CasesCollectionTest.php index 2418606..f22c519 100644 --- a/tests/CasesCollectionTest.php +++ b/tests/CasesCollectionTest.php @@ -121,6 +121,13 @@ ->toBe([PureEnum::one, 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]); +}); + it('retrieves a collection of cases with the given names', function () { expect((new CasesCollection(PureEnum::cases()))->only('one', 'three')) ->toBeInstanceOf(CasesCollection::class) diff --git a/tests/PureEnumTest.php b/tests/PureEnumTest.php index c0672b3..b52341b 100644 --- a/tests/PureEnumTest.php +++ b/tests/PureEnumTest.php @@ -62,6 +62,13 @@ ->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]); +}); + it('retrieves a collection of cases having the given names', function () { expect(PureEnum::only('two', 'three')) ->toBeInstanceOf(CasesCollection::class) From dd5f3888bc4e2170411bf2e4567ba0f29100db5b Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 12 Jul 2022 15:51:34 +0200 Subject: [PATCH 20/21] Update readme --- README.md | 193 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 169 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index d0247a3..853d5bb 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ composer require cerbero/enum * [Comparison](#comparison) * [Keys resolution](#keys-resolution) * [Hydration](#hydration) +* [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: @@ -151,7 +152,7 @@ enum BackedEnum: int } ``` -The keys defined in this enum are `name`, `value` (as it is a backed enum), `color`, `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 `get()`: ```php PureEnum::one->get('name'); // 'one' @@ -174,7 +175,7 @@ BackedEnum::one->color(); // 'red' BackedEnum::one->isOdd(); // true ``` -However `get()` is useful to resolve keys dynamically as a key may be a property, a method or a closure. It also gets called internally for more advanced functionalities that we are going to explore very soon. +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 @@ -190,20 +191,14 @@ PureEnum::fromName('one'); // PureEnum::one PureEnum::fromName('four'); // throws ValueError PureEnum::tryFromName('one'); // PureEnum::one PureEnum::tryFromName('four'); // null -PureEnum::fromKey('name', 'one'); // PureEnum::one +PureEnum::fromKey('name', 'one'); // CasesCollection PureEnum::fromKey('value', 1); // throws ValueError -PureEnum::fromKey('color', 'red'); // PureEnum::one +PureEnum::fromKey('color', 'red'); // CasesCollection PureEnum::fromKey(fn (PureEnum $case) => $case->isOdd(), true); // CasesCollection -PureEnum::tryFromKey('name', 'one'); // PureEnum::one +PureEnum::tryFromKey('name', 'one'); // CasesCollection PureEnum::tryFromKey('value', 1); // null -PureEnum::tryFromKey('color', 'red'); // PureEnum::one +PureEnum::tryFromKey('color', 'red'); // CasesCollection PureEnum::tryFromKey(fn (PureEnum $case) => $case->isOdd(), true); // CasesCollection -PureEnum::fromColor('red'); // PureEnum::one -PureEnum::fromInvalid('invalid'); // throws ValueError -PureEnum::fromIsOdd(); // CasesCollection -PureEnum::tryFromColor('red'); // PureEnum::one -PureEnum::tryFromInvalid('invalid'); // null -PureEnum::tryFromIsOdd(); // CasesCollection BackedEnum::from(1); // BackedEnum::one BackedEnum::from('1'); // throws ValueError @@ -213,20 +208,170 @@ BackedEnum::fromName('one'); // BackedEnum::one BackedEnum::fromName('four'); // throws ValueError BackedEnum::tryFromName('one'); // BackedEnum::one BackedEnum::tryFromName('four'); // null -BackedEnum::fromKey('name', 'one'); // BackedEnum::one -BackedEnum::fromKey('value', 1); // BackedEnum::one -BackedEnum::fromKey('color', 'red'); // BackedEnum::one +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'); // BackedEnum::one -BackedEnum::tryFromKey('value', 1); // BackedEnum::one -BackedEnum::tryFromKey('color', 'red'); // BackedEnum::one +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::fromColor('red'); // BackedEnum::one -BackedEnum::fromInvalid('invalid'); // throws ValueError -BackedEnum::fromIsOdd(); // CasesCollection -BackedEnum::tryFromColor('red'); // BackedEnum::one -BackedEnum::tryFromInvalid('invalid'); // null -BackedEnum::tryFromIsOdd(); // 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. + + +### Elaborating cases + +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: + +```php +PureEnum::collect(); // CasesCollection +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::values(); // [] +PureEnum::pluck(); // ['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::sortDescByValue(); // CasesCollection<> +PureEnum::sortBy('color'); // CasesCollection +PureEnum::sortDescBy(fn (PureEnum $case) => $case->color()); // CasesCollection + +BackedEnum::collect(); // CasesCollection +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::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('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::sortDescByValue(); // CasesCollection +BackedEnum::sortBy('color'); // CasesCollection +BackedEnum::sortDescBy(fn (BackedEnum $case) => $case->color()); // CasesCollection +``` + + +### 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: + +```php +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`: + +```php +PureEnum::collect(); // CasesCollection + +BackedEnum::only('one', 'two'); // CasesCollection +``` + +We can iterate cases collections within any loop: + +```php +foreach (PureEnum::collect() as $case) { + echo $case->name; +} +``` + +Obtaining the underlying plain list of cases is easy: + +```php +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 +``` + +For reference, here are all the operations available in `CasesCollection`: + +```php +PureEnum::collect()->cases(); // [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()->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()->sortDescByValue(); // CasesCollection<> +PureEnum::collect()->sortBy('color'); // CasesCollection +PureEnum::collect()->sortDescBy(fn (PureEnum $case) => $case->color()); // CasesCollection + +BackedEnum::collect()->cases(); // [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()->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()->sortDescByValue(); // CasesCollection +BackedEnum::collect()->sortBy('color'); // CasesCollection +BackedEnum::collect()->sortDescBy(fn (BackedEnum $case) => $case->color()); // CasesCollection ``` ## ๐Ÿ“† Change log From 635123fa2698484b82e9f70e9bf69075f0ff200e Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Tue, 12 Jul 2022 15:59:59 +0200 Subject: [PATCH 21/21] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65826af..bf7906b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,3 +20,9 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi ### Security - Nothing + + +## 1.0.0 - 2022-07-12 + +### Added +- First implementation of the package