diff --git a/README.md b/README.md index 7093421..d45ea65 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,16 @@ enum ExampleEnum { return self::findAttribute(CustomStuff::class, $this); } + + public function getCustomWithTrue(): CustomStuff|null + { + return self::findAttribute(CustomStuff::class, $this, static fn (CustomStuff $s) => $s->thing); + } } ``` See the trait for more methods. +You can use the `$filter` parameter to better revise results, so that you don't have to create multiple classes to gain the same functionality. Attributes are preloaded once per enum and stored in AttributeCache. diff --git a/src/AttributeControlTrait.php b/src/AttributeControlTrait.php index e87d838..4b61fdc 100644 --- a/src/AttributeControlTrait.php +++ b/src/AttributeControlTrait.php @@ -10,14 +10,29 @@ trait AttributeControlTrait * @template T of CacheableAttributeInterface * * @param class-string $class + * @param null|callable(T): bool $filter whether to return attribute * * @return list */ protected static function findAttributes( string $class, - \UnitEnum $case + \UnitEnum $case, + callable|null $filter = null ): array { - return AttributeCache::instance()->get(static::class)[$class][$case->name] ?? []; // @phpstan-ignore-line + /** @var list $attributes */ + $attributes = AttributeCache::instance()->get(static::class)[$class][$case->name] ?? []; // @phpstan-ignore-line + + if (null !== $filter) { + foreach ($attributes as $index => $attribute) { + if (!$filter($attribute)) { + unset($attributes[$index]); + } + } + + $attributes = array_values($attributes); + } + + return $attributes; } /** @@ -26,14 +41,16 @@ protected static function findAttributes( * @template T of CacheableAttributeInterface * * @param class-string $class + * @param null|callable(T): bool $filter whether to return attribute * * @return T|null */ protected static function findAttribute( string $class, - \UnitEnum $case + \UnitEnum $case, + callable|null $filter = null ): object|null { - return self::findAttributes($class, $case)[0] ?? null; + return self::findAttributes($class, $case, $filter)[0] ?? null; } /** @@ -42,12 +59,14 @@ protected static function findAttribute( * @template T of CacheableAttributeInterface * * @param class-string $class + * @param null|callable(T): bool $filter whether to return attribute */ protected static function attributeExists( string $class, - \UnitEnum $case + \UnitEnum $case, + callable|null $filter = null ): bool { - return null !== self::findAttribute($class, $case); + return null !== self::findAttribute($class, $case, $filter); } /** @@ -56,15 +75,32 @@ protected static function attributeExists( * @template T of CacheableAttributeInterface * * @param class-string $class + * @param null|callable(T): bool $filter whether to return case * * @return list */ protected static function findCases( - string $class + string $class, + callable|null $filter = null ): array { - return array_map( - static fn (string $case) => self::{$case}, - array_keys(AttributeCache::instance()->get(static::class)[$class]) - ); + $results = AttributeCache::instance()->get(static::class)[$class]; + + if (null !== $filter) { + $keys = []; + + foreach ($results as $case => $attributes) { + /** @var T $attribute */ + foreach ($attributes as $attribute) { + if ($filter($attribute)) { + $keys[] = $case; + break; + } + } + } + } else { + $keys = array_keys($results); + } + + return array_map(static fn (string $case) => self::{$case}, $keys); } } diff --git a/tests/Enum/ExposedEnum.php b/tests/Enum/ExposedEnum.php index 2497fb1..dc9c43b 100644 --- a/tests/Enum/ExposedEnum.php +++ b/tests/Enum/ExposedEnum.php @@ -24,52 +24,60 @@ enum ExposedEnum * @template T of CacheableAttributeInterface * * @param class-string $class + * @param null|callable(T): bool $filter * * @return list */ public static function tFindAttributes( string $class, - self $case + self $case, + callable|null $filter = null ): array { - return self::findAttributes($class, $case); + return self::findAttributes($class, $case, $filter); } /** * @template T of CacheableAttributeInterface * * @param class-string $class + * @param null|callable(T): bool $filter * * @return T|null */ public static function tFindAttribute( string $class, - self $case + self $case, + callable|null $filter = null ): object|null { - return self::findAttribute($class, $case); + return self::findAttribute($class, $case, $filter); } /** * @template T of CacheableAttributeInterface * * @param class-string $class + * @param null|callable(T): bool $filter */ public static function tAttributeExists( string $class, - self $case + self $case, + callable|null $filter = null ): bool { - return self::attributeExists($class, $case); + return self::attributeExists($class, $case, $filter); } /** * @template T of CacheableAttributeInterface * * @param class-string $class + * @param null|callable(T): bool $filter * * @return list */ public static function tFindCases( - string $class + string $class, + callable|null $filter = null ): array { - return self::findCases($class); + return self::findCases($class, $filter); } } diff --git a/tests/Unit/ExposedEnumTest.php b/tests/Unit/ExposedEnumTest.php index 4594f07..0c10ec1 100644 --- a/tests/Unit/ExposedEnumTest.php +++ b/tests/Unit/ExposedEnumTest.php @@ -32,6 +32,26 @@ public function testFindAttributes(): void ); } + public function testFindAttributesWithFilter(): void + { + $callback = static fn (CustomStuff $s) => $s->thing; + + static::assertEmpty( + ExposedEnum::tFindAttributes(CustomStuff::class, ExposedEnum::Bar, $callback) + ); + static::assertCount( + 1, + $found = ExposedEnum::tFindAttributes(CustomStuff::class, ExposedEnum::Foo, $callback) + ); + static::assertInstanceOf( + CustomStuff::class, + $found[0] + ); + static::assertTrue( + $found[0]->thing + ); + } + public function testFindAttribute(): void { static::assertNull( @@ -46,6 +66,22 @@ public function testFindAttribute(): void ); } + public function testFindAttributeWithFilter(): void + { + $callback = static fn (CustomStuff $s) => !$s->thing; + + static::assertNull( + ExposedEnum::tFindAttribute(CustomStuff::class, ExposedEnum::Bar, $callback) + ); + static::assertInstanceOf( + CustomStuff::class, + $output = ExposedEnum::tFindAttribute(CustomStuff::class, ExposedEnum::Foo, $callback) + ); + static::assertFalse( + $output->thing + ); + } + public function testAttributeExists(): void { static::assertFalse( @@ -63,6 +99,16 @@ public function testAttributeExists(): void ); } + public function testAttributeExistsWithFilter(): void + { + static::assertFalse( + ExposedEnum::tAttributeExists(NotAllowed::class, ExposedEnum::Bar, static fn () => false) + ); + static::assertFalse( + ExposedEnum::tAttributeExists(CustomStuff::class, ExposedEnum::Foo, static fn () => false) + ); + } + public function testFindCases(): void { static::assertEquals( @@ -72,4 +118,17 @@ public function testFindCases(): void ExposedEnum::tFindCases(CustomStuff::class) ); } + + public function testFindCasesWithFilter(): void + { + static::assertEquals( + [ + ExposedEnum::Foo, + ], + ExposedEnum::tFindCases(CustomStuff::class, static fn (CustomStuff $s) => $s->thing), + ); + static::assertEmpty( + ExposedEnum::tFindCases(CustomStuff::class, static fn () => false) + ); + } }