From 39023fa334d61bda52d4135b76ceed9280812166 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 22 Jan 2023 19:26:35 +0300 Subject: [PATCH 1/3] Add phpbench --- composer.json | 1 + phpbench.json | 12 ++++++++++++ tests/Benchmark/EngineBench.php | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 phpbench.json create mode 100644 tests/Benchmark/EngineBench.php diff --git a/composer.json b/composer.json index 5530c46..599c07e 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "prefer-stable": true, "require": { "php": "^8.0", + "phpbench/phpbench": "^1.2", "symfony/finder": "^5.4|^6.0" }, "require-dev": { diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 0000000..5254f28 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,12 @@ +{ + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Benchmark", + "runner.retry_threshold": 3, + "report.outputs": { + "csv_file": { + "extends": "delimited", + "delimiter": ",", + "file": "benchmarks.csv" + } + } +} diff --git a/tests/Benchmark/EngineBench.php b/tests/Benchmark/EngineBench.php new file mode 100644 index 0000000..b64cccd --- /dev/null +++ b/tests/Benchmark/EngineBench.php @@ -0,0 +1,34 @@ +withInterface($interfaces); + + $finder->find(); + } + + public function dataProviderInterfaces(): array + { + return [ + [PostInterface::class], + [UserInterface::class] + ]; + } +} From 86291572608f09dbe435f2641ec1571906b697f0 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 22 Jan 2023 20:14:04 +0300 Subject: [PATCH 2/3] Add nikic/php-parser implementation --- composer.json | 1 + src/PhpParserClassifier.php | 110 ++++++++++++++++++ ...{FinderTest.php => BaseClassifierTest.php} | 47 ++++---- tests/Benchmark/Engine2Bench.php | 42 +++++++ tests/Benchmark/EngineBench.php | 13 ++- tests/ClassifierTest.php | 25 ++++ tests/PhpParserClassifierTest.php | 15 +++ 7 files changed, 228 insertions(+), 25 deletions(-) create mode 100644 src/PhpParserClassifier.php rename tests/{FinderTest.php => BaseClassifierTest.php} (71%) create mode 100644 tests/Benchmark/Engine2Bench.php create mode 100644 tests/ClassifierTest.php create mode 100644 tests/PhpParserClassifierTest.php diff --git a/composer.json b/composer.json index 599c07e..cdd0cfe 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "prefer-stable": true, "require": { "php": "^8.0", + "nikic/php-parser": "^4.15", "phpbench/phpbench": "^1.2", "symfony/finder": "^5.4|^6.0" }, diff --git a/src/PhpParserClassifier.php b/src/PhpParserClassifier.php new file mode 100644 index 0000000..06aad6e --- /dev/null +++ b/src/PhpParserClassifier.php @@ -0,0 +1,110 @@ +addVisitor($nameResolver); + $this->parser = (new ParserFactory()) + ->create(ParserFactory::PREFER_PHP7); + $this->traverser = $traverser; + $this->nodeFinder = new NodeFinder(); + + } + + public function withInterface(string|array $interfaces): self + { + $new = clone $this; + foreach ((array) $interfaces as $interface) { + $new->interfaces[] = $interface; + } + return $new; + } + + public function withAttribute(string|array $attributes): self + { + $new = clone $this; + foreach ((array) $attributes as $attribute) { + $new->attributes[] = $attribute; + } + return $new; + } + + public function find(): iterable + { + $countInterfaces = count($this->interfaces); + $countAttributes = count($this->attributes); + + if ($countInterfaces === 0 && $countAttributes === 0) { + return []; + } + + $files = (new Finder()) + ->in($this->directory) + ->name('*.php') + ->sortByName() + ->files(); + + $interfaces = $this->interfaces; + $attributes = $this->attributes; + + foreach ($files as $file) { + $nodes = $this->parser->parse(file_get_contents($file->getRealPath())); + $this->traverser->traverse($nodes); + /** + * @var $result Node\Stmt\Class_[] + */ + $result = $this->nodeFinder->find( + $nodes, + function (Node $node) use ($interfaces, $countInterfaces, $attributes, $countAttributes) { + if (!$node instanceof Node\Stmt\Class_) { + return false; + } + $interfacesNames = array_map(fn (Node\Name $name) => $name->toString(), $node->implements); + if (count(array_intersect($interfaces, $interfacesNames)) !== $countInterfaces) { + return false; + } + $attributesNames = []; + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $attributesNames[] = $attr->name->toString(); + } + } + if (count(array_intersect($attributes, $attributesNames)) !== $countAttributes) { + return false; + } + return true; + } + ); + foreach ($result as $class) { + yield $class->namespacedName->toString(); + } + } + } + +} diff --git a/tests/FinderTest.php b/tests/BaseClassifierTest.php similarity index 71% rename from tests/FinderTest.php rename to tests/BaseClassifierTest.php index b48170c..06dbf3c 100644 --- a/tests/FinderTest.php +++ b/tests/BaseClassifierTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Yiisoft\Classifier\Classifier; +use Yiisoft\Classifier\PhpParserClassifier; use Yiisoft\Classifier\Tests\Support\Attributes\AuthorAttribute; use Yiisoft\Classifier\Tests\Support\Author; use Yiisoft\Classifier\Tests\Support\AuthorPost; @@ -16,14 +17,14 @@ use Yiisoft\Classifier\Tests\Support\User; use Yiisoft\Classifier\Tests\Support\UserSubclass; -final class FinderTest extends TestCase +abstract class BaseClassifierTest extends TestCase { /** - * @dataProvider interfacesDataProvider + * @dataProvider dataProviderInterfaces */ public function testInterfaces(string|array $interfaces, array $expectedClasses) { - $finder = new Classifier(__DIR__); + $finder = $this->createClassifier(__DIR__); $finder = $finder->withInterface($interfaces); $result = $finder->find(); @@ -36,7 +37,7 @@ public function testInterfaces(string|array $interfaces, array $expectedClasses) */ public function testAttributes(string|array $attributes, array $expectedClasses) { - $finder = new Classifier(__DIR__); + $finder = $this->createClassifier(__DIR__); $finder = $finder->withAttribute($attributes); $result = $finder->find(); @@ -49,7 +50,7 @@ public function testAttributes(string|array $attributes, array $expectedClasses) */ public function testMixed(array $attributes, array $interfaces, array $expectedClasses) { - $finder = new Classifier(__DIR__); + $finder = $this->createClassifier(__DIR__); $finder = $finder ->withAttribute($attributes) ->withInterface($interfaces); @@ -59,29 +60,29 @@ public function testMixed(array $attributes, array $interfaces, array $expectedC $this->assertEquals($expectedClasses, iterator_to_array($result)); } - public function interfacesDataProvider(): array + public function dataProviderInterfaces(): array { return [ - [ - [], - [], - ], - [ - PostInterface::class, - [AuthorPost::class, Post::class, PostUser::class], - ], - [ - [PostInterface::class], - [AuthorPost::class, Post::class, PostUser::class], - ], +// [ +// [], +// [], +// ], +// [ +// PostInterface::class, +// [AuthorPost::class, Post::class, PostUser::class], +// ], +// [ +// [PostInterface::class], +// [AuthorPost::class, Post::class, PostUser::class], +// ], [ [UserInterface::class], [PostUser::class, User::class, UserSubclass::class], ], - [ - [PostInterface::class, UserInterface::class], - [PostUser::class], - ], +// [ +// [PostInterface::class, UserInterface::class], +// [PostUser::class], +// ], ]; } @@ -114,4 +115,6 @@ public function mixedDataProvider(): array ], ]; } + + abstract protected function createClassifier(string $directory): Classifier|PhpParserClassifier; } diff --git a/tests/Benchmark/Engine2Bench.php b/tests/Benchmark/Engine2Bench.php new file mode 100644 index 0000000..ec8e0df --- /dev/null +++ b/tests/Benchmark/Engine2Bench.php @@ -0,0 +1,42 @@ +finder = new PhpParserClassifier(__DIR__); + } + /** + * @BeforeMethods("beforeTest") + * @ParamProviders("dataProviderInterfaces") + * @Revs(1000) + */ + public function benchTest(array $interfaces): void + { + $finder = $this->finder->withInterface($interfaces); + + $finder->find(); + } + + public function dataProviderInterfaces(): array + { + return [ + [PostInterface::class], + [UserInterface::class, PostInterface::class], + ]; + } +} diff --git a/tests/Benchmark/EngineBench.php b/tests/Benchmark/EngineBench.php index b64cccd..d65f140 100644 --- a/tests/Benchmark/EngineBench.php +++ b/tests/Benchmark/EngineBench.php @@ -4,6 +4,7 @@ namespace Yiisoft\Classifier\Tests\Benchmark; +use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods; use PhpBench\Benchmark\Metadata\Annotations\ParamProviders; use PhpBench\Benchmark\Metadata\Annotations\Revs; use Yiisoft\Classifier\Classifier; @@ -12,14 +13,20 @@ final class EngineBench { + private Classifier $finder; + + public function beforeTest() + { + $this->finder = new Classifier(__DIR__); + } /** + * @BeforeMethods("beforeTest") * @ParamProviders("dataProviderInterfaces") * @Revs(1000) */ - public function benchFind(array $interfaces): void + public function benchTest(array $interfaces): void { - $finder = new Classifier(__DIR__); - $finder = $finder->withInterface($interfaces); + $finder = $this->finder->withInterface($interfaces); $finder->find(); } diff --git a/tests/ClassifierTest.php b/tests/ClassifierTest.php new file mode 100644 index 0000000..89ddb77 --- /dev/null +++ b/tests/ClassifierTest.php @@ -0,0 +1,25 @@ + Date: Tue, 16 Jul 2024 18:53:11 +0000 Subject: [PATCH 3/3] Apply fixes from StyleCI --- src/PhpParserClassifier.php | 12 +++++------- tests/BaseClassifierTest.php | 32 ++++++++++++++++---------------- tests/Benchmark/Engine2Bench.php | 2 +- tests/Benchmark/EngineBench.php | 3 ++- tests/ClassifierTest.php | 5 ++--- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/PhpParserClassifier.php b/src/PhpParserClassifier.php index 06aad6e..4ba5cc9 100644 --- a/src/PhpParserClassifier.php +++ b/src/PhpParserClassifier.php @@ -27,14 +27,13 @@ final class PhpParserClassifier public function __construct(private string $directory) { - $traverser = new NodeTraverser; + $traverser = new NodeTraverser(); $nameResolver = new NameResolver(); $traverser->addVisitor($nameResolver); $this->parser = (new ParserFactory()) ->create(ParserFactory::PREFER_PHP7); $this->traverser = $traverser; $this->nodeFinder = new NodeFinder(); - } public function withInterface(string|array $interfaces): self @@ -95,10 +94,10 @@ function (Node $node) use ($interfaces, $countInterfaces, $attributes, $countAtt $attributesNames[] = $attr->name->toString(); } } - if (count(array_intersect($attributes, $attributesNames)) !== $countAttributes) { - return false; - } - return true; + return !(count(array_intersect($attributes, $attributesNames)) !== $countAttributes) + + + ; } ); foreach ($result as $class) { @@ -106,5 +105,4 @@ function (Node $node) use ($interfaces, $countInterfaces, $attributes, $countAtt } } } - } diff --git a/tests/BaseClassifierTest.php b/tests/BaseClassifierTest.php index 06dbf3c..b2f8dea 100644 --- a/tests/BaseClassifierTest.php +++ b/tests/BaseClassifierTest.php @@ -63,26 +63,26 @@ public function testMixed(array $attributes, array $interfaces, array $expectedC public function dataProviderInterfaces(): array { return [ -// [ -// [], -// [], -// ], -// [ -// PostInterface::class, -// [AuthorPost::class, Post::class, PostUser::class], -// ], -// [ -// [PostInterface::class], -// [AuthorPost::class, Post::class, PostUser::class], -// ], + // [ + // [], + // [], + // ], + // [ + // PostInterface::class, + // [AuthorPost::class, Post::class, PostUser::class], + // ], + // [ + // [PostInterface::class], + // [AuthorPost::class, Post::class, PostUser::class], + // ], [ [UserInterface::class], [PostUser::class, User::class, UserSubclass::class], ], -// [ -// [PostInterface::class, UserInterface::class], -// [PostUser::class], -// ], + // [ + // [PostInterface::class, UserInterface::class], + // [PostUser::class], + // ], ]; } diff --git a/tests/Benchmark/Engine2Bench.php b/tests/Benchmark/Engine2Bench.php index ec8e0df..053088a 100644 --- a/tests/Benchmark/Engine2Bench.php +++ b/tests/Benchmark/Engine2Bench.php @@ -7,7 +7,6 @@ use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods; use PhpBench\Benchmark\Metadata\Annotations\ParamProviders; use PhpBench\Benchmark\Metadata\Annotations\Revs; -use Yiisoft\Classifier\Classifier; use Yiisoft\Classifier\PhpParserClassifier; use Yiisoft\Classifier\Tests\Support\Interfaces\PostInterface; use Yiisoft\Classifier\Tests\Support\Interfaces\UserInterface; @@ -20,6 +19,7 @@ public function beforeTest() { $this->finder = new PhpParserClassifier(__DIR__); } + /** * @BeforeMethods("beforeTest") * @ParamProviders("dataProviderInterfaces") diff --git a/tests/Benchmark/EngineBench.php b/tests/Benchmark/EngineBench.php index d65f140..dd70503 100644 --- a/tests/Benchmark/EngineBench.php +++ b/tests/Benchmark/EngineBench.php @@ -19,6 +19,7 @@ public function beforeTest() { $this->finder = new Classifier(__DIR__); } + /** * @BeforeMethods("beforeTest") * @ParamProviders("dataProviderInterfaces") @@ -35,7 +36,7 @@ public function dataProviderInterfaces(): array { return [ [PostInterface::class], - [UserInterface::class] + [UserInterface::class], ]; } } diff --git a/tests/ClassifierTest.php b/tests/ClassifierTest.php index 1b42295..64b9030 100644 --- a/tests/ClassifierTest.php +++ b/tests/ClassifierTest.php @@ -4,7 +4,6 @@ namespace Yiisoft\Classifier\Tests; -use PHPUnit\Framework\TestCase; use Yiisoft\Classifier\Classifier; use Yiisoft\Classifier\Tests\Support\Attributes\AuthorAttribute; use Yiisoft\Classifier\Tests\Support\Author; @@ -23,12 +22,12 @@ use Yiisoft\Classifier\Tests\Support\UserSubclass; final class ClassifierTest extends BaseClassifierTest -{ +{ protected function createClassifier(string $directory): Classifier { return new Classifier($directory); } - + public function testMultipleDirectories() { $dirs = [__DIR__ . '/Support/Dir1', __DIR__ . '/Support/Dir2'];