From aceaaf9411de4bd382dba88405b3f6e7eafb5597 Mon Sep 17 00:00:00 2001 From: pkly Date: Fri, 27 Feb 2026 09:06:38 +0100 Subject: [PATCH 1/3] Allow filtering via callback functions --- src/AttributeControlTrait.php | 56 +++++++++++++++++++++++++------- tests/Enum/ExposedEnum.php | 24 +++++++++----- tests/Unit/ExposedEnumTest.php | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 19 deletions(-) diff --git a/src/AttributeControlTrait.php b/src/AttributeControlTrait.php index e87d838..54134ec 100644 --- a/src/AttributeControlTrait.php +++ b/src/AttributeControlTrait.php @@ -10,14 +10,28 @@ 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 + $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 +40,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 +58,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 +74,31 @@ 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) { + 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) + ); + } } From a4bf61f88362337da04f9acb6ab11e4ba0e2ec13 Mon Sep 17 00:00:00 2001 From: pkly Date: Fri, 27 Feb 2026 09:12:16 +0100 Subject: [PATCH 2/3] Update readme, fix qc issue --- README.md | 6 ++++++ src/AttributeControlTrait.php | 2 ++ 2 files changed, 8 insertions(+) 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 54134ec..cb49f7a 100644 --- a/src/AttributeControlTrait.php +++ b/src/AttributeControlTrait.php @@ -22,6 +22,7 @@ protected static function findAttributes( $attributes = AttributeCache::instance()->get(static::class)[$class][$case->name] ?? []; // @phpstan-ignore-line if (null !== $filter) { + /** @var T $attribute */ foreach ($attributes as $index => $attribute) { if (!$filter($attribute)) { unset($attributes[$index]); @@ -88,6 +89,7 @@ protected static function findCases( $keys = []; foreach ($results as $case => $attributes) { + /** @var T $attribute */ foreach ($attributes as $attribute) { if ($filter($attribute)) { $keys[] = $case; From 9d9d697bdf86461ebb1b303bbd5cc64e807b79ea Mon Sep 17 00:00:00 2001 From: pkly Date: Fri, 27 Feb 2026 09:15:08 +0100 Subject: [PATCH 3/3] Update readme, fix qc issue --- src/AttributeControlTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AttributeControlTrait.php b/src/AttributeControlTrait.php index cb49f7a..4b61fdc 100644 --- a/src/AttributeControlTrait.php +++ b/src/AttributeControlTrait.php @@ -19,10 +19,10 @@ protected static function findAttributes( \UnitEnum $case, callable|null $filter = null ): array { + /** @var list $attributes */ $attributes = AttributeCache::instance()->get(static::class)[$class][$case->name] ?? []; // @phpstan-ignore-line if (null !== $filter) { - /** @var T $attribute */ foreach ($attributes as $index => $attribute) { if (!$filter($attribute)) { unset($attributes[$index]);