diff --git a/composer.json b/composer.json index 78cbcce..834e952 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,6 @@ "require": { "php": ">=7.2", "nikic/php-parser": "^4", - "symfony/finder": "^4|^5" + "symfony/finder": "^4|^5|^6" } } diff --git a/src/Iterator/ClassIterator.php b/src/Iterator/ClassIterator.php index bae6f3f..087bf8c 100644 --- a/src/Iterator/ClassIterator.php +++ b/src/Iterator/ClassIterator.php @@ -20,6 +20,7 @@ use hanneskod\classtools\Iterator\Filter\NotFilter; use hanneskod\classtools\Iterator\Filter\TypeFilter; use hanneskod\classtools\Iterator\Filter\WhereFilter; +use hanneskod\classtools\Iterator\Filter\AttributeFilter; use hanneskod\classtools\Exception\LogicException; use hanneskod\classtools\Loader\ClassLoader; use hanneskod\classtools\Exception\ReaderException; @@ -144,6 +145,11 @@ public function cache(): Filter return $this->filter(new CacheFilter); } + public function attribute(string $attribute_class_name): Filter + { + return $this->filter(new AttributeFilter($attribute_class_name)); + } + public function transform(Writer $writer): string { $code = ''; diff --git a/src/Iterator/ClassIteratorInterface.php b/src/Iterator/ClassIteratorInterface.php index 0f8e7cd..a6c7346 100644 --- a/src/Iterator/ClassIteratorInterface.php +++ b/src/Iterator/ClassIteratorInterface.php @@ -84,6 +84,11 @@ public function not(Filter $filter): Filter; */ public function cache(): Filter; + /** + * Attribute iterator + */ + public function attribute(string $attribute_class_name): Filter; + /** * Transform found classes */ diff --git a/src/Iterator/Filter/AttributeFilter.php b/src/Iterator/Filter/AttributeFilter.php new file mode 100644 index 0000000..0a16c4a --- /dev/null +++ b/src/Iterator/Filter/AttributeFilter.php @@ -0,0 +1,68 @@ +attribute(CoolStuff::class) as $class) { + * echo $class->getName(); + * } + * + * Note this works only in PHP 8.0.1 or higher, but syntax is PHP 7 compaptible + * so whilst this cannot be used on earlier versions it also won't break them + * + * @author Mark Hewitt + */ +final class AttributeFilter extends ClassIterator implements Filter +{ + use FilterTrait; + + /** + * @var string + */ + private $attribute_class_name; + + /** + * Register matching attribute name + */ + public function __construct(string $attribute_class_name) + { + parent::__construct(); + $this->attribute_class_name = $attribute_class_name; + } + + public function getIterator(): iterable + { + foreach ($this->getBoundIterator() as $className => $reflectedClass) { + // if the class implements the given attribute (one or more times) + // then this is a match, and we yield with the found class + if ( !empty($reflectedClass->getAttributes($this->attribute_class_name, \ReflectionAttribute::IS_INSTANCEOF)) ) { + yield $className => $reflectedClass; + } + } + } +} diff --git a/tests/Iterator/ClassIteratorTest.php b/tests/Iterator/ClassIteratorTest.php index 9bb4e90..55fef75 100644 --- a/tests/Iterator/ClassIteratorTest.php +++ b/tests/Iterator/ClassIteratorTest.php @@ -246,6 +246,79 @@ public function testCacheFilter() ); } + public function testAttributeFilter() + { + if ( version_compare(PHP_VERSION, '8.0.1') >= 0) { + + # define our own system under test, adding attributes etc to standard test will break + # tests for minimize etc + MockFinder::setIterator( + new \ArrayIterator([ + new MockSplFileInfo(''), + new MockSplFileInfo('enableAutoloading(); + + // look for class tagged with 'CoolStuff' + // we should find both BeCool, TheCoolest and WayCool because filter uses instance inheritance + // by default, thus WayCool extends CoolStuff and is detect by this filter + $result = iterator_to_array( + $classIterator->attribute('CoolStuff') + ); + $this->assertArrayHasKey( + 'BeCool', + $result + ); + $this->assertArrayHasKey( + 'TheCoolest', + $result + ); + $this->assertArrayHasKey( + 'InheritedCoolness', + $result + ); + $this->assertCount( + 3, + $result + ); + + // look for class tagged with 'EvenCoolerStuff' + // we should find only TheCoolest + $result = iterator_to_array( + $classIterator->attribute('EvenCoolerStuff') + ); + $this->assertArrayHasKey( + 'TheCoolest', + $result + ); + $this->assertCount( + 1, + $result + ); + + // look for an attribute not tagged onto anything, should return empty + $result = iterator_to_array( + $classIterator->attribute('NotUsed') + ); + $this->assertEmpty( + $result + ); + + } else { + $this->markTestSkipped('This test requires PHP 8.0.1 or higher'); + } + } + public function testMinimize() {