diff --git a/.github/workflows/performance-tests.yml b/.github/workflows/performance-tests.yml index fac8f64..5e83562 100644 --- a/.github/workflows/performance-tests.yml +++ b/.github/workflows/performance-tests.yml @@ -15,7 +15,7 @@ jobs: operating-system: [ ubuntu-latest ] php-version: [ '8.1', '8.2' ] - name: PHP ${{ matrix.php-version }} test on ${{ matrix.operating-system }} + name: PHP ${{ matrix.php-version }} performance test on ${{ matrix.operating-system }} steps: - name: Checkout Code @@ -27,6 +27,7 @@ jobs: with: php-version: ${{ matrix.php-version }} coverage: none + ini-values: opcache.enable_cli=1 - name: Check PHP version run: php -v @@ -45,4 +46,4 @@ jobs: run: composer install --prefer-dist --no-progress - name: PHPUnit Performance Tests - run: vendor/bin/phpunit --testsuite=Performance + run: vendor/bin/phpunit --testsuite=Performance --display-notices diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e3ca71b..b38bc99 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: operating-system: [ ubuntu-latest ] php-version: [ '8.1', '8.2' ] - name: PHP ${{ matrix.php-version }} performance tests on ${{ matrix.operating-system }} + name: PHP ${{ matrix.php-version }} tests on ${{ matrix.operating-system }} steps: - name: Checkout Code @@ -45,7 +45,7 @@ jobs: run: composer install --prefer-dist --no-progress - name: PHPUnit Tests - run: vendor/bin/phpunit --testsuite=Tests --coverage-clover ./tests/coverage.xml + run: vendor/bin/phpunit --testsuite=Tests --coverage-clover ./tests/coverage.xml --display-notices - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 diff --git a/README.md b/README.md index 371b53c..9e4d7cf 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,6 @@ composer require okapi/aop - [How it works](#how-it-works) - [Testing](#testing) - [Contributing](#contributing) -- [Roadmap](#roadmap) @@ -793,12 +792,6 @@ class EverythingAspect -## Roadmap - -See [Roadmap](https://github.com/okapi-web/php-aop/issues/9) for more details. - - - ## Show your support Give a ⭐ if this project helped you! diff --git a/composer.json b/composer.json index 7770351..31aa37e 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "okapi/aop", "description": "PHP AOP is a PHP library that provides a powerful Aspect Oriented Programming (AOP) implementation for PHP.", - "version": "1.2.6", + "version": "1.2.7", "type": "library", "homepage": "https://github.com/okapi-web/php-aop", "license": "MIT", @@ -19,14 +19,14 @@ "php-aop" ], "scripts": { - "test": "phpunit --testsuite=Tests", - "test-performance": "phpunit --testsuite=Performance", - "test-coverage": "phpunit --coverage-html tests/coverage" + "test": "phpunit --testsuite=Tests --display-notices", + "test-performance": "phpunit --testsuite=Performance --display-notices", + "test-coverage": "phpunit --testsuite=Tests --coverage-html tests/coverage --display-notices" }, "require": { "php": ">=8.1", "nette/php-generator": "^4.0", - "okapi/code-transformer": "^1.3", + "okapi/code-transformer": "^1.3.4", "okapi/wildcards": "^1.0", "okapi/singleton": "^1.0", "php-di/php-di": "^7.0" diff --git a/phpunit.xml b/phpunit.xml index 3c44a84..a8acfbd 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,6 +7,7 @@ tests/Functional + tests/Integration diff --git a/src/AopKernel.php b/src/AopKernel.php index 4d12bc5..dab40d7 100644 --- a/src/AopKernel.php +++ b/src/AopKernel.php @@ -30,6 +30,13 @@ * * The AOP Kernel is the heart of the AOP library. * It manages an environment for Aspect Oriented Programming. + * + * 1. Extend this class and define a list of aspects in the {@link $aspects} + * property. + * 2. Call the {@link init()} method early in the application lifecycle. + * + * If you want to modify the kernel options dynamically, override the + * {@link configureOptions()} method. */ abstract class AopKernel extends CodeTransformerKernel { diff --git a/src/Core/AutoloadInterceptor/ClassLoader.php b/src/Core/AutoloadInterceptor/ClassLoader.php index c07fee1..8c45b12 100644 --- a/src/Core/AutoloadInterceptor/ClassLoader.php +++ b/src/Core/AutoloadInterceptor/ClassLoader.php @@ -6,6 +6,7 @@ use Okapi\Aop\Core\Matcher\AspectMatcher; use Okapi\CodeTransformer\Core\AutoloadInterceptor; use Okapi\CodeTransformer\Core\AutoloadInterceptor\ClassLoader as CodeTransformerClassLoader; +use Okapi\CodeTransformer\Core\Options\Environment; use Okapi\CodeTransformer\Core\StreamFilter; use Okapi\CodeTransformer\Core\StreamFilter\FilterInjector; use Okapi\Path\Path; @@ -35,6 +36,8 @@ class ClassLoader extends CodeTransformerClassLoader * @param class-string $namespacedClass * * @return false|string + * + * @noinspection PhpStatementHasEmptyBodyInspection */ public function findFile($namespacedClass): false|string { @@ -58,13 +61,28 @@ public function findFile($namespacedClass): false|string // Query cache state $cacheState = $this->cacheStateManager->queryCacheState($filePath); - // If the cache is cached and up to date - if ($cacheState?->isFresh() && !$this->options->isDebug()) { + // When debugging, bypass the caching mechanism + if ($this->options->isDebug()) { + // ... + } + + // In production mode, use the cache without checking if it is fresh + elseif ($this->options->getEnvironment() === Environment::PRODUCTION + && $cacheState + ) { // Use the cached file if aspects have been applied // Or return the original file if no aspects have been applied return $cacheState->getFilePath() ?? $filePath; } + // In development mode, check if the cache is fresh + elseif ($this->options->getEnvironment() === Environment::DEVELOPMENT + && $cacheState + && $cacheState->isFresh() + ) { + return $cacheState->getFilePath() ?? $filePath; + } + // Match the aspects $matchedAspects = $this->aspectMatcher->matchByClassLoader( diff --git a/src/Core/Container/AspectManager.php b/src/Core/Container/AspectManager.php index fa1978c..d9f7125 100644 --- a/src/Core/Container/AspectManager.php +++ b/src/Core/Container/AspectManager.php @@ -112,7 +112,9 @@ public function loadAspect(mixed $aspectClassName): void { // Check if the aspect is already loaded if (array_key_exists($aspectClassName, $this->aspectAdviceContainers)) { + // @codeCoverageIgnoreStart return; + // @codeCoverageIgnoreEnd } // Validate the aspect diff --git a/src/Core/Invocation/AdviceChainAwareTrait.php b/src/Core/Invocation/AdviceChainAwareTrait.php index 52b0b46..67ee5c2 100644 --- a/src/Core/Invocation/AdviceChainAwareTrait.php +++ b/src/Core/Invocation/AdviceChainAwareTrait.php @@ -28,14 +28,12 @@ public function setAdviceChain(AdviceChain $adviceChain): void /** * Call next advice or target method. * - * @param bool $allowRepeatedCalls
If {@see true}, the original method - * will be called again.
- * If {@see false}, the original method will - * be called only once and every subsequent - * call will return the same result.
- * Default: {@see false}
- * WARNING: May cause unexpected behavior - * and side effects. + * @param bool $allowRepeatedCalls + *
If {@see true}, the original method will be called again.
+ * If {@see false}, the original method will be called only once and every + * subsequent call will return the same result.
+ * Default: {@see false}
+ * WARNING: May cause unexpected behavior and side effects. * * @return mixed */ diff --git a/tests/ClassLoaderMockTrait.php b/tests/ClassLoaderMockTrait.php new file mode 100644 index 0000000..a75271d --- /dev/null +++ b/tests/ClassLoaderMockTrait.php @@ -0,0 +1,91 @@ +classLoader)) { + $this->findClassLoader(); + } + + return $this->classLoader->findFile($class); + } + + private function findOriginalClassMock(string $class): string + { + if (!isset($this->classLoader)) { + $this->findClassLoader(); + } + + $original = new ReflectionProperty(ClassLoader::class, 'originalClassLoader'); + $original = $original->getValue($this->classLoader); + return $original->findFile($class); + } + + private function findClassLoader(): void + { + foreach (spl_autoload_functions() as $function) { + if (is_array($function) && $function[0] instanceof ClassLoader) { + $this->classLoader = $function[0]; + break; + } + } + } + + public function assertWillBeWoven(string $className): void + { + $originalFilePath = Path::resolve($this->findOriginalClassMock($className)); + + $wovenPath = + FilterInjector::PHP_FILTER_READ . + StreamFilter::FILTER_ID . '/resource=' . + $originalFilePath; + + $filePathMock = $this->findClassMock($className); + + Assert::assertEquals( + $wovenPath, + $filePathMock, + "$className will not be woven", + ); + } + + public function assertAspectLoadedFromCache(string $className): void + { + $filePath = $this->findOriginalClassMock($className); + $cachePaths = DI::get(CachePaths::class); + $cachePath = $cachePaths->getProxyCachePath($filePath); + $filePathMock = $this->findClassMock($className); + + Assert::assertEquals( + $cachePath, + $filePathMock, + "$className will not be loaded from cache", + ); + } + + public function assertAspectNotApplied(string $className): void + { + $originalFilePath = Path::resolve($this->findOriginalClassMock($className)); + $filePathMock = $this->findClassMock($className); + + Assert::assertEquals( + $originalFilePath, + $filePathMock, + "$className will be woven", + ); + } +} diff --git a/tests/Functional/AbstractMethod/AbstractMethodTest.php b/tests/Functional/AbstractMethod/AbstractMethodTest.php deleted file mode 100644 index a94c1a2..0000000 --- a/tests/Functional/AbstractMethod/AbstractMethodTest.php +++ /dev/null @@ -1,32 +0,0 @@ -upload('C:\Windows\Temp\file.txt'); - - $this->assertEquals( - 'C:/Windows/Temp/file.txt', - $result - ); - } -} diff --git a/tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/AdviceMatchingAbstractMethodTest.php b/tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/AdviceMatchingAbstractMethodTest.php new file mode 100644 index 0000000..c304684 --- /dev/null +++ b/tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/AdviceMatchingAbstractMethodTest.php @@ -0,0 +1,37 @@ +assertWillBeWoven(LocalFileUploader::class); + + $uploader = new LocalFileUploader(); + + $result = $uploader->upload('C:\Windows\Temp\file.txt'); + + /** @noinspection PhpConditionAlreadyCheckedInspection */ + $this->assertEquals( + 'C:/Windows/Temp/file.txt', + $result + ); + } +} diff --git a/tests/Functional/AbstractMethod/Aspect/FileUploaderAspect.php b/tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/Aspect/FileUploaderAspect.php similarity index 72% rename from tests/Functional/AbstractMethod/Aspect/FileUploaderAspect.php rename to tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/Aspect/FileUploaderAspect.php index 55524fa..aaf223d 100644 --- a/tests/Functional/AbstractMethod/Aspect/FileUploaderAspect.php +++ b/tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/Aspect/FileUploaderAspect.php @@ -1,11 +1,11 @@ assertWillBeWoven(InventoryTracker::class); + $this->executeTest(); } @@ -25,6 +30,8 @@ public function testCachedExplicitClassLevelAspect(): void { EmptyKernel::init(); + $this->assertAspectLoadedFromCache(InventoryTracker::class); + $this->executeTest(); } diff --git a/tests/Functional/ExplicitClassLevelAspect/ClassesToMatch/InventoryTracker.php b/tests/Functional/AdviceApplication/ExplicitClassLevelAspect/Target/InventoryTracker.php similarity index 65% rename from tests/Functional/ExplicitClassLevelAspect/ClassesToMatch/InventoryTracker.php rename to tests/Functional/AdviceApplication/ExplicitClassLevelAspect/Target/InventoryTracker.php index a5cb876..5667ea1 100644 --- a/tests/Functional/ExplicitClassLevelAspect/ClassesToMatch/InventoryTracker.php +++ b/tests/Functional/AdviceApplication/ExplicitClassLevelAspect/Target/InventoryTracker.php @@ -1,8 +1,8 @@ assertWillBeWoven(CustomerService::class); + $this->executeTest(); } @@ -27,20 +31,26 @@ public function testCachedNotRegisteredExplicitMethodLevelAspect(): void { EmptyKernel::init(); + $this->assertAspectLoadedFromCache(CustomerService::class); + $this->executeTest(); } public function testRegisteredExplicitMethodLevelAspect(): void { Util::clearCache(); - ExplicitAspectsKernel::init(); + Kernel::init(); + + $this->assertWillBeWoven(CustomerService::class); $this->executeTest(); } public function testCachedExplicitMethodLevelAspect(): void { - ExplicitAspectsKernel::init(); + Kernel::init(); + + $this->assertAspectLoadedFromCache(CustomerService::class); $this->executeTest(); } diff --git a/tests/Functional/AdviceApplication/ExplicitMethodLevelAspect/Kernel.php b/tests/Functional/AdviceApplication/ExplicitMethodLevelAspect/Kernel.php new file mode 100644 index 0000000..36c32b0 --- /dev/null +++ b/tests/Functional/AdviceApplication/ExplicitMethodLevelAspect/Kernel.php @@ -0,0 +1,16 @@ +assertWillBeWoven(AccountService::class); $accountService = new AccountService(); $accountService->createAccount(['id' => $id]); @@ -40,6 +44,7 @@ public function testMultipleExplicitMethodLevelAspects(): void $this->assertCount(0, $accounts); + $this->assertWillBeWoven(TransactionService::class); $transactionService = new TransactionService(); $transactionService->createTransaction(['id' => $id]); diff --git a/tests/Functional/MultipleExplicitMethodLevelAspects/ClassesToIntercept/AccountService.php b/tests/Functional/AdviceApplication/MultipleExplicitMethodLevelAspects/Target/AccountService.php similarity index 77% rename from tests/Functional/MultipleExplicitMethodLevelAspects/ClassesToIntercept/AccountService.php rename to tests/Functional/AdviceApplication/MultipleExplicitMethodLevelAspects/Target/AccountService.php index bb2014f..3226ad4 100644 --- a/tests/Functional/MultipleExplicitMethodLevelAspects/ClassesToIntercept/AccountService.php +++ b/tests/Functional/AdviceApplication/MultipleExplicitMethodLevelAspects/Target/AccountService.php @@ -1,9 +1,9 @@ assertWillBeWoven(ArticleManager::class); + $articleManager = new ArticleManager(); $articleManager->createArticle( 'Hello World', diff --git a/tests/Functional/AdviceOptions/AdviceOrder/Aspect/ArticleModerationAspect.php b/tests/Functional/AdviceBehavior/AdviceOrder/Aspect/ArticleModerationAspect.php similarity index 87% rename from tests/Functional/AdviceOptions/AdviceOrder/Aspect/ArticleModerationAspect.php rename to tests/Functional/AdviceBehavior/AdviceOrder/Aspect/ArticleModerationAspect.php index ddee7d0..ddb4e2b 100644 --- a/tests/Functional/AdviceOptions/AdviceOrder/Aspect/ArticleModerationAspect.php +++ b/tests/Functional/AdviceBehavior/AdviceOrder/Aspect/ArticleModerationAspect.php @@ -1,11 +1,11 @@ assertWillBeWoven(Calculator::class); $calculator = new Calculator(); $result = $calculator->add(2, 3); diff --git a/tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameAdviceMethod/Kernel.php b/tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameAdviceMethod/Kernel.php new file mode 100644 index 0000000..e00921b --- /dev/null +++ b/tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameAdviceMethod/Kernel.php @@ -0,0 +1,16 @@ +assertWillBeWoven(PaymentProcessor::class); + $processor = new PaymentProcessor(); // Test with an invalid payment amount $amount = -50.00; diff --git a/tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameTargetMethod/Kernel.php b/tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameTargetMethod/Kernel.php new file mode 100644 index 0000000..823018a --- /dev/null +++ b/tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameTargetMethod/Kernel.php @@ -0,0 +1,16 @@ +assertWillBeWoven(EmailSender::class); $emailSender = new EmailSender(); $recipient = 'test@test.com'; @@ -31,6 +34,7 @@ public function testClassHierarchyAspect(): void $this->assertTrue($result); + $this->assertAspectNotApplied(SmsSender::class); $smsSender = new SmsSender(); $recipient = '123456789'; diff --git a/tests/Functional/AdviceBehavior/ClassHierarchyAspect/Kernel.php b/tests/Functional/AdviceBehavior/ClassHierarchyAspect/Kernel.php new file mode 100644 index 0000000..a98ee87 --- /dev/null +++ b/tests/Functional/AdviceBehavior/ClassHierarchyAspect/Kernel.php @@ -0,0 +1,16 @@ +assertWillBeWoven(CommentController::class); + $commentController = new CommentController(); $commentController->saveComment('This is a good comment'); $this->assertTrue(true); diff --git a/tests/Functional/AdviceBehavior/ExceptionInsideAdvice/Kernel.php b/tests/Functional/AdviceBehavior/ExceptionInsideAdvice/Kernel.php new file mode 100644 index 0000000..6aeedde --- /dev/null +++ b/tests/Functional/AdviceBehavior/ExceptionInsideAdvice/Kernel.php @@ -0,0 +1,16 @@ +assertWillBeWoven(User::class); $user = new User(); $userName = $user->getName(); diff --git a/tests/Functional/AdviceBehavior/InterfaceAdvice/Kernel.php b/tests/Functional/AdviceBehavior/InterfaceAdvice/Kernel.php new file mode 100644 index 0000000..e46c4b8 --- /dev/null +++ b/tests/Functional/AdviceBehavior/InterfaceAdvice/Kernel.php @@ -0,0 +1,16 @@ +assertWillBeWoven(NumberHelper::class); $numberHelper = new NumberHelper(); $numbers = [1, 2, 3, 4, 5]; diff --git a/tests/Functional/ModifyArgument/ClassesToIntercept/NumberHelper.php b/tests/Functional/AdviceBehavior/ModifyArgument/Target/NumberHelper.php similarity index 63% rename from tests/Functional/ModifyArgument/ClassesToIntercept/NumberHelper.php rename to tests/Functional/AdviceBehavior/ModifyArgument/Target/NumberHelper.php index 60f646d..d702a2b 100644 --- a/tests/Functional/ModifyArgument/ClassesToIntercept/NumberHelper.php +++ b/tests/Functional/AdviceBehavior/ModifyArgument/Target/NumberHelper.php @@ -1,6 +1,6 @@ assertWillBeWoven(ProfileController::class); + $profileController = new ProfileController(); // Valid avatar $path = $profileController->uploadProfilePicture( @@ -44,7 +48,6 @@ public function testMultipleBeforeAdvicesOnSameTargetMethod(): void // Invalid avatar size $exceptionThrown = false; try { - /** @noinspection PhpExpressionResultUnusedInspection */ $profileController->uploadProfilePicture( 'avatar', self::AVATAR_HQ_PATH, @@ -61,7 +64,6 @@ public function testMultipleBeforeAdvicesOnSameTargetMethod(): void // Invalid avatar format $exceptionThrown = false; try { - /** @noinspection PhpExpressionResultUnusedInspection */ $profileController->uploadProfilePicture( 'avatar', self::AVATAR_WRONG_FORMAT_PATH, diff --git a/tests/Functional/MultipleBeforeAdvicesOnSameTargetMethod/ClassesToIntercept/ProfileController.php b/tests/Functional/AdviceBehavior/MultipleAdvicesWithSameAdviceTypeOnSameTargetMethod/Target/ProfileController.php similarity index 70% rename from tests/Functional/MultipleBeforeAdvicesOnSameTargetMethod/ClassesToIntercept/ProfileController.php rename to tests/Functional/AdviceBehavior/MultipleAdvicesWithSameAdviceTypeOnSameTargetMethod/Target/ProfileController.php index 0c6eb01..e3570b2 100644 --- a/tests/Functional/MultipleBeforeAdvicesOnSameTargetMethod/ClassesToIntercept/ProfileController.php +++ b/tests/Functional/AdviceBehavior/MultipleAdvicesWithSameAdviceTypeOnSameTargetMethod/Target/ProfileController.php @@ -1,6 +1,6 @@ assertWillBeWoven(TargetClass::class); $targetClass = new TargetClass(); $targetClass->helloWorld(); diff --git a/tests/Functional/AdviceOptions/OnlyPublicMethods/Target/TargetClass.php b/tests/Functional/AdviceBehavior/OnlyPublicMethods/Target/TargetClass.php similarity index 81% rename from tests/Functional/AdviceOptions/OnlyPublicMethods/Target/TargetClass.php rename to tests/Functional/AdviceBehavior/OnlyPublicMethods/Target/TargetClass.php index 856fc4e..ecec896 100644 --- a/tests/Functional/AdviceOptions/OnlyPublicMethods/Target/TargetClass.php +++ b/tests/Functional/AdviceBehavior/OnlyPublicMethods/Target/TargetClass.php @@ -1,6 +1,6 @@ assertWillBeWoven(BankingSystem::class); $bankingSystem = new BankingSystem(); $bankingSystem->deposit(100.0); diff --git a/tests/Functional/ProtectedAndPrivateMethods/ClassesToIntercept/BankingSystem.php b/tests/Functional/AdviceBehavior/ProtectedAndPrivateMethods/Target/BankingSystem.php similarity index 90% rename from tests/Functional/ProtectedAndPrivateMethods/ClassesToIntercept/BankingSystem.php rename to tests/Functional/AdviceBehavior/ProtectedAndPrivateMethods/Target/BankingSystem.php index 47e21e6..059cb5d 100644 --- a/tests/Functional/ProtectedAndPrivateMethods/ClassesToIntercept/BankingSystem.php +++ b/tests/Functional/AdviceBehavior/ProtectedAndPrivateMethods/Target/BankingSystem.php @@ -1,6 +1,6 @@ assertWillBeWoven(Router::class); $router = new Router(); $routes = $router->getRoutes(); diff --git a/tests/Functional/VariadicParameters/Aspect/StringPrefixerAspect.php b/tests/Functional/AdviceBehavior/VariadicParameters/Aspect/StringPrefixerAspect.php similarity index 79% rename from tests/Functional/VariadicParameters/Aspect/StringPrefixerAspect.php rename to tests/Functional/AdviceBehavior/VariadicParameters/Aspect/StringPrefixerAspect.php index dd773ac..d2aceda 100644 --- a/tests/Functional/VariadicParameters/Aspect/StringPrefixerAspect.php +++ b/tests/Functional/AdviceBehavior/VariadicParameters/Aspect/StringPrefixerAspect.php @@ -1,12 +1,12 @@ assertWillBeWoven(IdHelper::class); + $idHelper = new IdHelper(); $result = $idHelper->createIds('prefix', ...$ids); @@ -36,7 +41,7 @@ public function testVariadicParametersWithoutAop(): void $ids = ['id1', 'id2', 'id3']; - $idHelper = new ClassesToIntercept\IdHelper(); + $idHelper = new IdHelper(); $result = $idHelper->createIds('prefix', ...$ids); diff --git a/tests/Functional/AdviceOptions/InterceptTraitMethods/Kernel/Kernel.php b/tests/Functional/AdviceOptions/InterceptTraitMethods/Kernel/Kernel.php deleted file mode 100644 index 12e7519..0000000 --- a/tests/Functional/AdviceOptions/InterceptTraitMethods/Kernel/Kernel.php +++ /dev/null @@ -1,18 +0,0 @@ -assertWillBeWoven(Product::class); + $product = new Product(); $productPrice = $product->getPrice(); $this->assertEquals(90.00, $productPrice); - $order = new ClassesToIntercept\Order(); + $this->assertWillBeWoven(Order::class); + $order = new Order(); $orderTotal = $order->getTotal(); $this->assertEquals(400.00, $orderTotal); } public function testCachedAdviceMatchingMultipleClassesAndMethods(): void { - ApplicationKernel::init(); + Kernel::init(); - $product = new ClassesToIntercept\Product(); + $this->assertAspectLoadedFromCache(Product::class); + $product = new Product(); $productPrice = $product->getPrice(); $this->assertEquals(90.00, $productPrice); - $order = new ClassesToIntercept\Order(); + $this->assertAspectLoadedFromCache(Order::class); + $order = new Order(); $orderTotal = $order->getTotal(); $this->assertEquals(400.00, $orderTotal); } diff --git a/tests/Functional/AdviceMatchingMultipleClassesAndMethods/Aspect/DiscountAspect.php b/tests/Functional/AspectMatching/AdviceMatchingMultipleClassesAndMethods/Aspect/DiscountAspect.php similarity index 75% rename from tests/Functional/AdviceMatchingMultipleClassesAndMethods/Aspect/DiscountAspect.php rename to tests/Functional/AspectMatching/AdviceMatchingMultipleClassesAndMethods/Aspect/DiscountAspect.php index fde97b3..d378b47 100644 --- a/tests/Functional/AdviceMatchingMultipleClassesAndMethods/Aspect/DiscountAspect.php +++ b/tests/Functional/AspectMatching/AdviceMatchingMultipleClassesAndMethods/Aspect/DiscountAspect.php @@ -1,12 +1,12 @@ assertWillBeWoven(TargetClassC::class); $instance = new TargetClassA(); $instance->helloWorld(); $instance->helloWorld(); diff --git a/tests/Functional/AspectMatching/ClassHierarchyOnlyInvokedOnce/Kernel.php b/tests/Functional/AspectMatching/ClassHierarchyOnlyInvokedOnce/Kernel.php new file mode 100644 index 0000000..56eafaf --- /dev/null +++ b/tests/Functional/AspectMatching/ClassHierarchyOnlyInvokedOnce/Kernel.php @@ -0,0 +1,15 @@ +assertWillBeWoven(TargetClass::class); + $this->assertAspectNotApplied(TargetTrait::class); $targetClass = new TargetClass(); $targetClass->helloWorld(); diff --git a/tests/Functional/AdviceOptions/InterceptTraitMethods/Aspect/DefaultAspect.php b/tests/Functional/AspectMatching/InterceptTraitMethods/Aspect/DefaultAspect.php similarity index 67% rename from tests/Functional/AdviceOptions/InterceptTraitMethods/Aspect/DefaultAspect.php rename to tests/Functional/AspectMatching/InterceptTraitMethods/Aspect/DefaultAspect.php index 2f2c74b..e30e90a 100644 --- a/tests/Functional/AdviceOptions/InterceptTraitMethods/Aspect/DefaultAspect.php +++ b/tests/Functional/AspectMatching/InterceptTraitMethods/Aspect/DefaultAspect.php @@ -1,6 +1,6 @@ assertAspectNotApplied(Car::class); $car = new Car(); /** @noinspection PhpDeprecationInspection */ $car->startCar(); diff --git a/tests/Functional/JetBrainsAttribute/ClassesToIntercept/Car.php b/tests/Functional/AspectMatching/JetBrainsAttribute/Target/Car.php similarity index 65% rename from tests/Functional/JetBrainsAttribute/ClassesToIntercept/Car.php rename to tests/Functional/AspectMatching/JetBrainsAttribute/Target/Car.php index cd7849f..cc64779 100644 --- a/tests/Functional/JetBrainsAttribute/ClassesToIntercept/Car.php +++ b/tests/Functional/AspectMatching/JetBrainsAttribute/Target/Car.php @@ -1,6 +1,6 @@ assertWillBeWoven(Employee::class); + $this->assertWillBeWoven(AbstractEmployee::class); $employee = new Employee('Walter', 3000.0); $salaryIncrease = 1000.0; @@ -45,7 +49,7 @@ public function testSelfType(): void $demotedEmployee = $promotedEmployee->demote($promotedEmployee, $salaryDecrease); $this->assertInstanceOf(PartTimeEmployee::class, $demotedEmployee); - $this->assertInstanceOf(ClassesToIntercept\Employee::class, $demotedEmployee); + $this->assertInstanceOf(Employee::class, $demotedEmployee); $this->assertInstanceOf(AbstractEmployee::class, $demotedEmployee); $this->assertSame( $promotedEmployee->getName(), diff --git a/tests/Functional/SelfType/ClassesToIntercept/AbstractEmployee.php b/tests/Functional/AspectMatching/SelfType/Target/AbstractEmployee.php similarity index 87% rename from tests/Functional/SelfType/ClassesToIntercept/AbstractEmployee.php rename to tests/Functional/AspectMatching/SelfType/Target/AbstractEmployee.php index 6b927d1..2f83916 100644 --- a/tests/Functional/SelfType/ClassesToIntercept/AbstractEmployee.php +++ b/tests/Functional/AspectMatching/SelfType/Target/AbstractEmployee.php @@ -1,6 +1,6 @@ assertTrue($class->checkIfFloat(1.0)); - } - - public function testCachedTransformerAndAspect(): void - { - TransformerAndAspectKernel::init(); - - $class = new ClassesToIntercept\DeprecatedAndWrongClass(); - $this->assertTrue($class->checkIfFloat(42.0)); - $this->assertFalse($class->checkIfFloat("Hello World!")); - } -} diff --git a/tests/Functional/VariadicParameters/ClassesToIntercept/IdHelper.php b/tests/Functional/VariadicParameters/ClassesToIntercept/IdHelper.php deleted file mode 100644 index c32e88a..0000000 --- a/tests/Functional/VariadicParameters/ClassesToIntercept/IdHelper.php +++ /dev/null @@ -1,15 +0,0 @@ -assertWillBeWoven(DeprecatedAndWrongClass::class); + $class = new DeprecatedAndWrongClass(); + $this->assertTrue($class->checkIfFloat(1.0)); + } + + public function testCachedTransformerAndAspect(): void + { + Kernel::init(); + + $this->assertAspectLoadedFromCache(DeprecatedAndWrongClass::class); + $class = new DeprecatedAndWrongClass(); + $this->assertTrue($class->checkIfFloat(42.0)); + $this->assertFalse($class->checkIfFloat("Hello World!")); + } +} diff --git a/tests/Performance/Aspect/AddOneAspect.php b/tests/Performance/Aspect/AddOneAspect.php index 8f7ab2b..fee703f 100644 --- a/tests/Performance/Aspect/AddOneAspect.php +++ b/tests/Performance/Aspect/AddOneAspect.php @@ -1,5 +1,5 @@ [], - self::MEASURE_TYPE_WITH_ASPECTS => [], - self::MEASURE_TYPE_WITH_CACHED_ASPECTS => [], + self::MEASURE_TYPE_NO_ASPECTS => [], + self::MEASURE_TYPE_ASPECTS => [], + self::MEASURE_TYPE_CACHED_ASPECTS => [], + self::MEASURE_TYPE_PRODUCTION => [], ]; private const MEASURE_TYPE_FROM_START_TO_END = 'From Start to End'; @@ -41,34 +48,26 @@ class MeasurePerformanceTest extends TestCase private const METRIC_TYPE_MEMORY = 'Memory'; public static array $aspectCountAndExecutionCount = [ - [1, 1], - [5, 5], - [20, 20], - [50, 50], - [100, 100], - [500, 100], - [100, 500], - [1000, 500], - [500, 1000], - [1000, 1000], - [1, 5000], - [5000, 1], - [5000, 1000], - [1000, 5000], - [5000, 5000], + [1, 1], // Minimal + [5, 5], // Small + [20, 20], // Moderate + [50, 50], // Medium + [100, 100], // Common + [500, 100], // Common: Aspects++ + [100, 500], // Common: Executions++ + [500, 500], // High + [1000, 500], // High: Aspects++ + [500, 1000], // High: Executions++ + [1000, 1000], // Very High ]; - /** - * @return array Count of array should be: - * count($aspectCountAndExecutionCount) * count($flags) + 1 - * E.g. 13 * 3 + 1 = 40 - */ public static function dataProvider(): array { $flags = [ - self::MEASURE_TYPE_WITHOUT_ASPECTS => ['useAspects' => false, 'cached' => false], - self::MEASURE_TYPE_WITH_ASPECTS => ['useAspects' => true, 'cached' => false], - self::MEASURE_TYPE_WITH_CACHED_ASPECTS => ['useAspects' => true, 'cached' => true], + self::MEASURE_TYPE_NO_ASPECTS => [], + self::MEASURE_TYPE_ASPECTS => ['useAspects' => true], + self::MEASURE_TYPE_CACHED_ASPECTS => ['useAspects' => true, 'cached' => true], + self::MEASURE_TYPE_PRODUCTION => ['useAspects' => true, 'cached' => true, 'production' => true], ]; $data = []; @@ -83,22 +82,37 @@ public static function dataProvider(): array $dataProviderLabel = "$measureType: $aspectCount $aspectsLabel, $executionCount $executionLabel"; $data[$dataProviderLabel] = [ - 'aspectCount' => $aspectCount, + 'aspectCount' => $aspectCount, 'executionCount' => $executionCount, - 'useAspects' => $flag['useAspects'], - 'cached' => $flag['cached'], + 'useAspects' => $flag['useAspects'] ?? false, + 'cached' => $flag['cached'] ?? false, + 'production' => $flag['production'] ?? false, ]; } } // Cleanup data $data['Cleanup'] = [ - 'aspectCount' => 0, + 'aspectCount' => 0, 'executionCount' => 0, - 'useAspects' => false, - 'cached' => false, ]; + // Number of tests should equal the number of generated data sets + $dataCount = count($data); + $expectedDataCount = count(self::$aspectCountAndExecutionCount) * count($flags) + 1; + if ($dataCount !== $expectedDataCount) { + /** @noinspection PhpUnhandledExceptionInspection */ + throw new Exception("Expected $expectedDataCount data sets, got $dataCount"); + } + + if (extension_loaded('xdebug')) { + $input = new ArgvInput(); + $output = new ConsoleOutput(decorated: true); + $io = new SymfonyStyle($input, $output); + + $io->caution('Xdebug is enabled, which will slow down the tests'); + } + return $data; } @@ -108,15 +122,22 @@ public static function dataProvider(): array public function measurePerformance( int $aspectCount, int $executionCount, - bool $useAspects, - bool $cached + bool $useAspects = false, + bool $cached = false, + bool $production = false, ): void { + $noFlags = !$useAspects && !$cached && !$production; + $lastMeasure = $useAspects && $cached && $production; + $firstRun = $aspectCount === self::$aspectCountAndExecutionCount[0][0] && $executionCount === self::$aspectCountAndExecutionCount[0][1] - && !$useAspects - && !$cached; + && $noFlags; + $shouldCleanCache = $firstRun || ($useAspects && !$cached); - $lastRun = $aspectCount === 0 && $executionCount === 0 && !$useAspects && !$cached; + + $lastRun = $aspectCount === 0 + && $executionCount === 0 + && $noFlags; if ($firstRun) { $this->cleanup(); @@ -132,14 +153,19 @@ public function measurePerformance( return; } - /** - * @var class-string $kernel - * @var class-string[] $classes - */ - [$kernel, $classes] = $this->createKernelAspectsAndClasses($useAspects, $aspectCount); + /** @var class-string[] $services */ + $services = []; + if ($useAspects) { + // Create $aspectCount aspects and a kernel that uses them + $kernel = $this->createKernelAndAspects($aspectCount, $production); + } else { + // Emulate aspects by creating $aspectCount services + $services = $this->createServices($aspectCount); + } $this->useAspects = $useAspects; - $this->cached = $cached; + $this->cached = $cached; + $this->production = $production; $this->startMeasure(self::MEASURE_TYPE_FROM_START_TO_END); $this->startMeasure(self::MEASURE_TYPE_BOOT); @@ -152,10 +178,14 @@ public function measurePerformance( $this-> endMeasure(self::MEASURE_TYPE_BOOT); $this->startMeasure(self::MEASURE_TYPE_CLASS_LOADING); - /** @var Numbers[] $numberClasses */ - $numberClasses = []; - foreach ($classes as $class) { - $numberClasses[] = new $class(); + $numbersClass = new Numbers(); + + /** @var NumbersService[] $serviceInstances */ + $serviceInstances = []; + if (!$useAspects) { + foreach ($services as $service) { + $serviceInstances[] = new $service(); + } } $this->endMeasure(self::MEASURE_TYPE_CLASS_LOADING); @@ -165,15 +195,11 @@ public function measurePerformance( $this->startMeasure(self::MEASURE_TYPE_EXECUTION); // There are 2 loops here, that could be merged into one, but the - // performance difference is negligible, so it's not worth the + // performance difference is important, so it's not worth the // readability loss if ($useAspects) { - // There is only one numbers class when using aspects - /** @var Numbers $numbers */ - $numbers = $numberClasses[0]; - foreach (range(1, $executionCount) as $ignored) { - $result = $numbers->get(); + $result = $numbersClass->get(); // This is only used for validating the results, comment it out // $expectedResults[] = $aspectCount; @@ -181,15 +207,14 @@ public function measurePerformance( } } else { foreach (range(1, $aspectCount) as $i) { - /** @var Numbers $numbers */ - $numbers = $numberClasses[$i - 1]; + $numbersService = $serviceInstances[$i - 1]; foreach (range(1, $executionCount) as $ignored) { - // Here we emulate the aspect by adding 1 to the result - $numbers->add(1); + // Here we emulate the aspect by using a service + $numbersService->addToNumbers(1, $numbersClass); } - $result = $numbers->get(); + $result = $numbersClass->get(); // This is only used for validating the results, comment it out // $expectedResults[] = $executionCount; @@ -205,7 +230,7 @@ public function measurePerformance( $this->saveMeasuresToFile(); - if ($useAspects && $cached) { + if ($lastMeasure) { $this->printMeasures($this->dataName()); } @@ -213,167 +238,187 @@ public function measurePerformance( } /** - * @param bool $useAspects - * @param int $aspectCount - * - * @return array{class-string|null, class-string[]} + * @return class-string */ - private function createKernelAspectsAndClasses( - bool $useAspects, - int $aspectCount - ): array { + private function createKernelAndAspects( + int $aspectCount, + bool $production + ): string { $tempDirectory = __DIR__ . '/Temp'; if (!file_exists($tempDirectory)) { Filesystem::mkdir($tempDirectory); } - if ($useAspects) { - $newKernelFilePath = "$tempDirectory/MeasurePerformanceKernel$aspectCount.php"; - $newKernelFileNamespace = "\\Okapi\\Aop\\Tests\\Performance\\Temp\\MeasurePerformanceKernel$aspectCount"; - if (file_exists($newKernelFilePath)) { - return [$newKernelFileNamespace, [Numbers::class]]; - } + $newKernelFilePath = "$tempDirectory/MeasurePerformanceKernel$aspectCount.php"; + $newKernelFileNamespace = "\\Okapi\\Aop\\Tests\\Performance\\Temp\\MeasurePerformanceKernel$aspectCount"; + static $kernelFile; + if (!$kernelFile) { $kernelFile = Filesystem::readFile(__DIR__ . '/Kernel/MeasurePerformanceKernel.php'); + } - $addOneAspectLineNumber = 0; - $addOneAspectLine = ''; - $lines = explode("\n", $kernelFile); - foreach ($lines as $lineNumber => &$line) { - // Replace namespace - if (str_contains($line, 'namespace Okapi\\Aop\\Tests\\Performance\\Kernel')) { - $line = str_replace( - search: 'Kernel', - replace: 'Temp', - subject: $line, - ); - } - - // Replace class name - if (str_contains($line, 'class MeasurePerformanceKernel')) { - $line = str_replace( - search: 'MeasurePerformanceKernel', - replace: "MeasurePerformanceKernel$aspectCount", - subject: $line, - ); - } - - // Find the line where the "AddOneAspect" is added to the kernel - if (str_contains($line, 'AddOneAspect::class,')) { - $addOneAspectLineNumber = $lineNumber; - $addOneAspectLine = $line; - break; - } + $originalAspectLineNumber = 0; + $originalAspectLine = ''; + $lines = explode("\n", $kernelFile); + foreach ($lines as $lineNumber => &$line) { + // Replace namespace + if (str_contains($line, 'namespace Okapi\\Aop\\Tests\\Performance\\Kernel')) { + $line = str_replace( + search: 'Kernel', + replace: 'Temp', + subject: $line, + ); } - $aspects = []; - foreach (range(1, $aspectCount) as $aspectNumber) { - $aspects[] = str_replace( - search: 'Aspect\\AddOneAspect::class,', - replace: "Temp\\AddOneAspect$aspectNumber::class,", - subject: $addOneAspectLine, + // Replace class name + if (str_contains($line, 'class MeasurePerformanceKernel')) { + $line = str_replace( + search: 'MeasurePerformanceKernel', + replace: "MeasurePerformanceKernel$aspectCount", + subject: $line, ); + } - // Read aspect file - static $aspectFile; - if (!$aspectFile) { - $aspectFile = Filesystem::readFile(__DIR__ . '/Aspect/AddOneAspect.php'); - } - - $newAspectFilePath = __DIR__ . "/Temp/AddOneAspect$aspectNumber.php"; - if (file_exists($newAspectFilePath)) { - continue; - } - - $newAspectFile = $aspectFile; + // Find the line where the "AddOneAspect" is added to the kernel + if (str_contains($line, 'AddOneAspect::class,')) { + $originalAspectLineNumber = $lineNumber; + $originalAspectLine = $line; + break; + } - // Replace namespace - $newAspectFile = str_replace( - search: 'namespace Okapi\\Aop\\Tests\\Performance\\Aspect;', - replace: 'namespace Okapi\\Aop\\Tests\\Performance\\Temp;', - subject: $newAspectFile, + // Replace environment + if ($production && str_contains($line, 'Environment::DEVELOPMENT')) { + $line = str_replace( + search: 'Environment::DEVELOPMENT', + replace: 'Environment::PRODUCTION', + subject: $line, ); + } + } - // Replace class name - $newAspectFile = str_replace( - search: 'class AddOneAspect', - replace: "class AddOneAspect$aspectNumber", - subject: $newAspectFile, - ); + $aspects = []; + foreach (range(1, $aspectCount) as $aspectNumber) { + $aspects[] = str_replace( + search: 'Aspect\\AddOneAspect::class,', + replace: "Temp\\AddOneAspect$aspectNumber::class,", + subject: $originalAspectLine, + ); - // Write aspect file - Filesystem::writeFile( - $newAspectFilePath, - $newAspectFile, - ); + // Read aspect file + static $aspectFile; + if (!$aspectFile) { + $aspectFile = Filesystem::readFile(__DIR__ . '/Aspect/AddOneAspect.php'); } - unset($lines[$addOneAspectLineNumber]); + $newAspectFilePath = __DIR__ . "/Temp/AddOneAspect$aspectNumber.php"; + if (file_exists($newAspectFilePath)) { + continue; + } - array_splice($lines, $addOneAspectLineNumber, 0, $aspects); + $newAspectFile = $aspectFile; - $kernelFile = implode("\n", $lines); + // Replace namespace + $newAspectFile = str_replace( + search: 'namespace Okapi\\Aop\\Tests\\Performance\\Aspect;', + replace: 'namespace Okapi\\Aop\\Tests\\Performance\\Temp;', + subject: $newAspectFile, + ); - // Create Temp directory - if (!file_exists(__DIR__ . '/Temp')) { - Filesystem::mkdir(__DIR__ . '/Temp'); - } + // Replace class name + $newAspectFile = str_replace( + search: 'class AddOneAspect', + replace: "class AddOneAspect$aspectNumber", + subject: $newAspectFile, + ); - // Write kernel file + // Write aspect file Filesystem::writeFile( - $newKernelFilePath, - $kernelFile, + $newAspectFilePath, + $newAspectFile, ); - $this->dumpAutoload(); + $this->cacheFile($newAspectFilePath); + } - return [$newKernelFileNamespace, [Numbers::class]]; - } else { - $classes = []; - foreach (range(1, $aspectCount) as $aspectNumber) { - $classes[] = str_replace( - search: 'Target\\Numbers', - replace: "Temp\\Numbers$aspectNumber", - subject: Numbers::class, - ); + unset($lines[$originalAspectLineNumber]); // Remove the original aspect - // Read class file - static $classFile; - if (!$classFile) { - $classFile = Filesystem::readFile(__DIR__ . '/Target/Numbers.php'); - } + array_splice($lines, $originalAspectLineNumber, 0, $aspects); // Add the new aspects - $newClassFilePath = __DIR__ . "/Temp/Numbers$aspectNumber.php"; - if (file_exists($newClassFilePath)) { - continue; - } + $kernelFile = implode("\n", $lines); + + // Write kernel file + Filesystem::writeFile( + $newKernelFilePath, + $kernelFile, + ); - $newClassFile = $classFile; + $this->cacheFile($newKernelFilePath); - // Replace namespace - $newClassFile = str_replace( - search: 'namespace Okapi\\Aop\\Tests\\Performance\\Target;', - replace: 'namespace Okapi\\Aop\\Tests\\Performance\\Temp;', - subject: $newClassFile, - ); + $this->dumpAutoload(); - // Replace class name - $newClassFile = str_replace( - search: 'class Numbers', - replace: "class Numbers$aspectNumber", - subject: $newClassFile, - ); + return $newKernelFileNamespace; + } - // Write class file - Filesystem::writeFile( - $newClassFilePath, - $newClassFile, - ); + /** + * @return class-string[] + */ + private function createServices(int $serviceCount): array + { + $tempDirectory = __DIR__ . '/Temp'; + if (!file_exists($tempDirectory)) { + Filesystem::mkdir($tempDirectory); + } + + $services = []; + foreach (range(1, $serviceCount) as $serviceNumber) { + // Read service file + static $serviceFile; + if (!$serviceFile) { + $serviceFile = Filesystem::readFile(__DIR__ . '/Service/NumbersService.php'); + } + + $serviceNamespace = "\\Okapi\\Aop\\Tests\\Performance\\Temp\\NumbersService$serviceNumber"; + $services[] = $serviceNamespace; + + $newServiceFilePath = __DIR__ . "/Temp/NumbersService$serviceNumber.php"; + if (file_exists($newServiceFilePath)) { + continue; } - $this->dumpAutoload(); + $newServiceFile = $serviceFile; + + // Replace namespace + $newServiceFile = str_replace( + search: 'namespace Okapi\\Aop\\Tests\\Performance\\Service;', + replace: 'namespace Okapi\\Aop\\Tests\\Performance\\Temp;', + subject: $newServiceFile, + ); + + // Replace class name + $newServiceFile = str_replace( + search: 'class NumbersService', + replace: "class NumbersService$serviceNumber", + subject: $newServiceFile, + ); + + // Write service file + Filesystem::writeFile( + $newServiceFilePath, + $newServiceFile, + ); + + $this->cacheFile($newServiceFilePath); + } + + $this->dumpAutoload(); - return [null, $classes]; + return $services; + } + + private function cacheFile(string $filename): void + { + if (function_exists('opcache_compile_file')) { + opcache_compile_file($filename); } } @@ -429,17 +474,19 @@ private function saveMeasuresToFile(): void private function getMeasureType(): string { + if ($this->production) { + return self::MEASURE_TYPE_PRODUCTION; + } + + if ($this->cached) { + return self::MEASURE_TYPE_CACHED_ASPECTS; + } + if ($this->useAspects) { - if ($this->cached) { - $type = self::MEASURE_TYPE_WITH_CACHED_ASPECTS; - } else { - $type = self::MEASURE_TYPE_WITH_ASPECTS; - } - } else { - $type = self::MEASURE_TYPE_WITHOUT_ASPECTS; + return self::MEASURE_TYPE_ASPECTS; } - return $type; + return self::MEASURE_TYPE_NO_ASPECTS; } // region Print Measures @@ -451,52 +498,74 @@ private function printMeasures(string $dataProviderLabel): void associative: true, ); + // Remove the last measure, because it's the cleanup $dataProviderLabel = str_replace( - search: self::MEASURE_TYPE_WITH_CACHED_ASPECTS . ': ', + search: array_key_last($this->measures) . ': ', replace: '', subject: $dataProviderLabel, ); - $output = new ConsoleOutput(); + $input = new ArgvInput(); + $output = new ConsoleOutput(decorated: true); + $io = new SymfonyStyle($input, $output); + + $output->writeln(''); + + $io->section($dataProviderLabel); // First Table - $output->writeln("Table 1: Without Aspects vs With Aspects ($dataProviderLabel)"); - $this->printTable($output, self::MEASURE_TYPE_WITH_ASPECTS); + $output->writeln("Table 1: Without Aspects vs With Aspects ($dataProviderLabel)"); + $this->printTable($output, self::MEASURE_TYPE_ASPECTS); // Second Table $output->writeln(''); - $output->writeln("Table 2: Without Aspects vs With Cached Aspects ($dataProviderLabel)"); - $this->printTable($output, self::MEASURE_TYPE_WITH_CACHED_ASPECTS); + $output->writeln("Table 2: Without Aspects vs With Cached Aspects ($dataProviderLabel)"); + $this->printTable($output, self::MEASURE_TYPE_CACHED_ASPECTS); + + // Third Table $output->writeln(''); + $output->writeln("Table 3: Without Aspects vs Production ($dataProviderLabel)"); + $this->printTable($output, self::MEASURE_TYPE_PRODUCTION); + + // Fourth Table $output->writeln(''); + $output->writeln("Table 4: With Cached Aspects vs Production ($dataProviderLabel)"); + $this->printTable($output, self::MEASURE_TYPE_PRODUCTION, self::MEASURE_TYPE_CACHED_ASPECTS); + $output->writeln(''); } - private function printTable(ConsoleOutput $output, string $comparisonAspect): void - { + private function printTable( + ConsoleOutput $output, + string $comparisonAspect, + string $compareToType = self::MEASURE_TYPE_NO_ASPECTS + ): void { $table = new Table($output); + $headers = [ 'Measure Type', 'Metric', - 'Without Aspects', + $compareToType, $comparisonAspect, 'Difference', ]; $table->setHeaders($headers); - $measuresWithoutAspects = $this->measures[self::MEASURE_TYPE_WITHOUT_ASPECTS]; - foreach ($measuresWithoutAspects as $measureType => $metrics) { + $measuresToCompareWith = $this->measures[$compareToType]; + foreach ($measuresToCompareWith as $measureType => $metrics) { $table->addRow($this->generateRowData( $measureType, $comparisonAspect, + $compareToType, self::METRIC_TYPE_TIME, )); } - foreach ($measuresWithoutAspects as $measureType => $metrics) { + foreach ($measuresToCompareWith as $measureType => $metrics) { $table->addRow($this->generateRowData( $measureType, $comparisonAspect, + $compareToType, self::METRIC_TYPE_MEMORY, )); } @@ -514,6 +583,7 @@ private function printTable(ConsoleOutput $output, string $comparisonAspect): vo private function generateRowData( string $measureType, string $comparisonAspect, + string $compareToType, string $metricType ): array { // Get start and end metrics @@ -526,8 +596,8 @@ private function generateRowData( : self::END_MEMORY; // Calculate without aspects - $withoutAspectsValue = $this->measures[self::MEASURE_TYPE_WITHOUT_ASPECTS][$measureType][$endMetric] - - $this->measures[self::MEASURE_TYPE_WITHOUT_ASPECTS][$measureType][$startMetric]; + $withoutAspectsValue = $this->measures[$compareToType][$measureType][$endMetric] + - $this->measures[$compareToType][$measureType][$startMetric]; // Calculate with aspects $comparisonValue = $this->measures[$comparisonAspect][$measureType][$endMetric] @@ -542,8 +612,8 @@ private function generateRowData( // Calculate difference $difference = $comparisonValue - $withoutAspectsValue; - // Time: 8 digits, Memory: 2 digits - $digits = $metricType === self::METRIC_TYPE_TIME ? 8 : 2; + // Time: 8 digits, Memory: 4 digits + $digits = $metricType === self::METRIC_TYPE_TIME ? 8 : 4; // Format digits $withoutAspectsValue = number_format($withoutAspectsValue, $digits); @@ -552,7 +622,7 @@ private function generateRowData( // Prefix difference with + or - $prefix = $difference > 0 ? '+' : ''; - $difference = "$prefix$difference"; + $differenceText = "$prefix$difference"; // Append unit if ($metricType === self::METRIC_TYPE_TIME) { @@ -562,14 +632,20 @@ private function generateRowData( } $withoutAspectsValue .= $append; $comparisonValue .= $append; - $difference .= $append; + $differenceText .= $append; + + if ($difference > 0) { + $differenceText = "$differenceText"; + } elseif ($difference < 0) { + $differenceText = "$differenceText"; + } return [ $measureType, $metricType, $withoutAspectsValue, $comparisonValue, - $difference, + $differenceText, ]; } diff --git a/tests/Performance/Service/NumbersService.php b/tests/Performance/Service/NumbersService.php new file mode 100644 index 0000000..560a130 --- /dev/null +++ b/tests/Performance/Service/NumbersService.php @@ -0,0 +1,13 @@ +add($number); + } +} diff --git a/tests/Stubs/Kernel/ApplicationKernel.php b/tests/Stubs/Kernel/ApplicationKernel.php deleted file mode 100644 index 21c8f5c..0000000 --- a/tests/Stubs/Kernel/ApplicationKernel.php +++ /dev/null @@ -1,38 +0,0 @@ -