├── .editorconfig ├── .github └── workflows │ ├── performance-tests.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── XDEBUG.md ├── composer.json ├── phpunit.xml ├── src ├── Advice │ └── AdviceType.php ├── AopKernel.php ├── Attributes │ ├── After.php │ ├── Around.php │ ├── Aspect.php │ └── Before.php ├── Component │ └── ComponentType.php ├── Core │ ├── Attributes │ │ ├── AdviceType │ │ │ └── MethodAdvice.php │ │ └── Base │ │ │ ├── BaseAdvice.php │ │ │ └── BaseAttribute.php │ ├── AutoloadInterceptor │ │ └── ClassLoader.php │ ├── Cache │ │ ├── CachePaths.php │ │ ├── CacheState │ │ │ └── WovenCacheState.php │ │ ├── CacheStateFactory.php │ │ └── CacheStateManager.php │ ├── Container │ │ ├── AdviceContainer.php │ │ ├── AdviceContainerFactory.php │ │ ├── AdviceType │ │ │ └── MethodAdviceContainer.php │ │ ├── AspectManager.php │ │ ├── JoinPoint │ │ │ └── MethodJoinPointContainer.php │ │ ├── JoinPointContainer.php │ │ └── TransformerManager.php │ ├── Exception │ │ ├── Advice │ │ │ ├── MissingClassNameException.php │ │ │ └── MissingMethodNameException.php │ │ ├── AdviceException.php │ │ ├── AopException.php │ │ ├── Aspect │ │ │ ├── AspectNotFoundException.php │ │ │ ├── InvalidAspectClassNameException.php │ │ │ └── MissingAspectAttributeException.php │ │ └── AspectException.php │ ├── Factory │ │ └── InvocationFactory.php │ ├── Intercept │ │ └── Interceptor.php │ ├── Invocation │ │ ├── AdviceChain.php │ │ └── AdviceChainAwareTrait.php │ ├── JoinPoint │ │ ├── JoinPoint.php │ │ ├── JoinPointHandler.php │ │ └── JoinPointInjector.php │ ├── Matcher │ │ ├── AdviceMatcher.php │ │ ├── AdviceMatcher │ │ │ ├── MatchedMethod.php │ │ │ └── MethodMatcher.php │ │ ├── AspectMatcher.php │ │ └── ClassMatcher.php │ ├── Options.php │ ├── Processor │ │ └── AspectProcessor.php │ └── Transform │ │ ├── ProxiedClassModifier.php │ │ └── WovenClassBuilder.php └── Invocation │ ├── AfterMethodInvocation.php │ ├── AroundMethodInvocation.php │ ├── BeforeMethodInvocation.php │ └── MethodInvocation.php └── tests ├── ClassLoaderMockTrait.php ├── Functional ├── AdviceApplication │ ├── AdviceMatchingAbstractMethod │ │ ├── AdviceMatchingAbstractMethodTest.php │ │ ├── Aspect │ │ │ └── FileUploaderAspect.php │ │ ├── Kernel.php │ │ └── Target │ │ │ ├── FileUploader.php │ │ │ └── LocalFileUploader.php │ ├── ExplicitClassLevelAspect │ │ ├── Aspect │ │ │ └── LoggingAspect.php │ │ ├── ExplicitClassLevelAspectTest.php │ │ └── Target │ │ │ └── InventoryTracker.php │ ├── ExplicitMethodLevelAspect │ │ ├── Aspect │ │ │ └── PerformanceAspect.php │ │ ├── ExplicitMethodLevelAspectTest.php │ │ ├── Kernel.php │ │ └── Target │ │ │ └── CustomerService.php │ └── MultipleExplicitMethodLevelAspects │ │ ├── Aspect │ │ └── SecurityAspect.php │ │ ├── MultipleExplicitMethodLevelAspectsTest.php │ │ └── Target │ │ ├── AccountService.php │ │ └── TransactionService.php ├── AdviceBehavior │ ├── AdviceOrder │ │ ├── AdviceOrderTest.php │ │ ├── Aspect │ │ │ └── ArticleModerationAspect.php │ │ ├── Kernel.php │ │ └── Target │ │ │ └── ArticleManager.php │ ├── BeforeAroundAfterAdviceOnSameAdviceMethod │ │ ├── Aspect │ │ │ └── CalculatorLoggerAspect.php │ │ ├── BeforeAroundAfterAdviceOnSameAdviceMethodTest.php │ │ ├── Kernel.php │ │ └── Target │ │ │ └── Calculator.php │ ├── BeforeAroundAfterAdviceOnSameTargetMethod │ │ ├── Aspect │ │ │ └── PaymentProcessorAspect.php │ │ ├── BeforeAroundAfterAdviceOnSameTargetMethodTest.php │ │ ├── Kernel.php │ │ └── Target │ │ │ └── PaymentProcessor.php │ ├── ClassHierarchyAspect │ │ ├── Aspect │ │ │ └── NotificationAspect.php │ │ ├── ClassHierarchyAspectTest.php │ │ ├── Kernel.php │ │ └── Target │ │ │ ├── EmailSender.php │ │ │ ├── EmailSenderInterface.php │ │ │ ├── SmsSender.php │ │ │ └── SmsSenderInterface.php │ ├── ExceptionInsideAdvice │ │ ├── Aspect │ │ │ └── CommentFilterAspect.php │ │ ├── ExceptionInsideAdviceTest.php │ │ ├── Kernel.php │ │ └── Target │ │ │ └── CommentController.php │ ├── Include │ │ ├── Aspect │ │ │ └── DatabaseModifierAspect.php │ │ ├── Database │ │ │ └── data.php │ │ ├── IncludeTest.php │ │ ├── Kernel.php │ │ └── Target │ │ │ └── SecureDatabaseService.php │ ├── InterfaceAdvice │ │ ├── Aspect │ │ │ └── UserInterfaceAspect.php │ │ ├── InterfaceAdviceTest.php │ │ ├── Kernel.php │ │ └── Target │ │ │ ├── User.php │ │ │ └── UserInterface.php │ ├── MagicConstants │ │ ├── Aspect │ │ │ ├── AspectOnClass.php │ │ │ ├── AspectOnParent.php │ │ │ └── AspectOnTrait.php │ │ ├── Kernel │ │ │ ├── KernelOnClass.php │ │ │ ├── KernelOnClassAndParent.php │ │ │ ├── KernelOnClassAndParentAndTrait.php │ │ │ ├── KernelOnClassAndTrait.php │ │ │ ├── KernelOnParent.php │ │ │ ├── KernelOnParentAndTrait.php │ │ │ └── KernelOnTrait.php │ │ ├── MagicConstantsTest.php │ │ └── Target │ │ │ ├── TargetClass.php │ │ │ ├── TargetClass82.php │ │ │ ├── TargetParent.php │ │ │ ├── TargetParent82.php │ │ │ ├── TargetTrait.php │ │ │ └── TargetTrait82.php │ ├── ModifyArgument │ │ ├── Aspect │ │ │ └── NumberHelperAspect.php │ │ ├── Kernel.php │ │ ├── ModifyArgumentTest.php │ │ └── Target │ │ │ └── NumberHelper.php │ ├── ModifyArgumentPassedByReference │ │ ├── Aspect │ │ │ └── AddMetadataToArrayAspect.php │ │ ├── Kernel.php │ │ ├── ModifyArgumentPassedByReferenceTest.php │ │ └── Target │ │ │ └── ArrayCreator.php │ ├── MultipleAdvicesWithSameAdviceTypeOnSameTargetMethod │ │ ├── Aspect │ │ │ └── ProfilePictureValidatorAspect.php │ │ ├── Kernel.php │ │ ├── MultipleAdvicesWithSameAdviceTypeOnSameTargetMethodTest.php │ │ └── Target │ │ │ └── ProfileController.php │ ├── NewClassCreationWithProxiedClasses │ │ ├── Aspect │ │ │ └── ModifyGroupPolicyAspect.php │ │ ├── Kernel.php │ │ ├── NewClassCreationWithProxiedClassesTest.php │ │ └── Target │ │ │ ├── GroupMemberService.php │ │ │ └── GroupPolicy.php │ ├── OnlyPublicMethods │ │ ├── Aspect │ │ │ ├── DefaultAspect.php │ │ │ └── OnlyPublicMethodsAspect.php │ │ ├── Kernel.php │ │ ├── OnlyPublicMethodsTest.php │ │ └── Target │ │ │ ├── TargetClass.php │ │ │ ├── TargetParentClass.php │ │ │ └── TargetTrait.php │ ├── ProtectedAndPrivateMethods │ │ ├── Aspect │ │ │ └── BankingAspect.php │ │ ├── Kernel.php │ │ ├── ProtectedAndPrivateMethodsTest.php │ │ └── Target │ │ │ └── BankingSystem.php │ ├── Readonly │ │ ├── Aspect │ │ │ └── ReadonlyAspect.php │ │ ├── Kernel.php │ │ ├── ReadonlyTest.php │ │ └── Target │ │ │ ├── ReadonlyClass.php │ │ │ └── ReadonlyPromotedProperties.php │ ├── TraitAdvice │ │ ├── Aspect │ │ │ └── RouteCachingAspect.php │ │ ├── Kernel.php │ │ ├── Target │ │ │ ├── RouteCaching.php │ │ │ └── Router.php │ │ └── TraitAdviceTest.php │ └── VariadicParameters │ │ ├── Aspect │ │ └── StringPrefixerAspect.php │ │ ├── Target │ │ └── IdHelper.php │ │ └── VariadicParametersTest.php ├── AspectMatching │ ├── AdviceMatchingMultipleClassesAndMethods │ │ ├── AdviceMatchingMultipleClassesAndMethodsTest.php │ │ ├── Aspect │ │ │ └── DiscountAspect.php │ │ ├── Kernel.php │ │ └── Target │ │ │ ├── Order.php │ │ │ └── Product.php │ ├── ClassHierarchyOnlyInvokedOnce │ │ ├── Aspect.php │ │ ├── ClassHierarchyOnlyInvokedOnceTest.php │ │ ├── Kernel.php │ │ └── Target │ │ │ ├── TargetClassA.php │ │ │ ├── TargetClassB.php │ │ │ └── TargetClassC.php │ ├── InterceptTraitMethods │ │ ├── AdviceInterceptTraitMethodsTest.php │ │ ├── Aspect │ │ │ ├── DefaultAspect.php │ │ │ └── InterceptTraitMethodsAspect.php │ │ ├── Kernel.php │ │ └── Target │ │ │ ├── TargetClass.php │ │ │ └── TargetTrait.php │ ├── JetBrainsAttribute │ │ ├── JetBrainsAttributeTest.php │ │ └── Target │ │ │ └── Car.php │ └── SelfType │ │ ├── Aspect │ │ └── SalaryIncreaserAspect.php │ │ ├── Kernel.php │ │ ├── SelfTypeTest.php │ │ └── Target │ │ ├── AbstractEmployee.php │ │ ├── Employee.php │ │ └── PartTimeEmployee.php ├── ErrorHandling │ ├── InvalidAspect │ │ ├── Aspect │ │ │ └── InvalidAspect.php │ │ ├── InvalidAspectTest.php │ │ └── Kernel │ │ │ ├── InvalidAspectClassKernel.php │ │ │ ├── InvalidAspectClassNameKernel.php │ │ │ └── InvalidAspectsTypeKernel.php │ └── MissingClassOrMethod │ │ ├── Aspect │ │ ├── AddItemLoggerAspect.php │ │ ├── GetQuantityLoggerAspect.php │ │ └── RemoveItemLoggerAspect.php │ │ ├── Kernel │ │ ├── AddItemKernel.php │ │ ├── GetQuantityKernel.php │ │ └── RemoveItemKernel.php │ │ ├── MissingClassOrMethodTest.php │ │ └── Target │ │ └── InventoryManager.php └── Kernel │ └── CustomDependencyInjectionHandler │ ├── Aspect.php │ ├── CustomDependencyInjectionHandlerTest.php │ ├── Kernel.php │ └── Target.php ├── Integration ├── TransformerAndAspect │ ├── Aspect │ │ └── FixWrongReturnValueAspect.php │ ├── Kernel.php │ ├── Target │ │ └── DeprecatedAndWrongClass.php │ ├── Transformer │ │ └── FixDeprecatedFunctionTransformer.php │ └── TransformerAndAspectTest.php └── TransformerAndAspectDependencyInjectionHandler │ ├── Aspect.php │ ├── Kernel.php │ ├── Target.php │ ├── Transformer.php │ └── TransformerAndAspectDependencyInjectionHandlerTest.php ├── Performance ├── .gitignore ├── Aspect │ └── AddOneAspect.php ├── Kernel │ └── MeasurePerformanceKernel.php ├── MeasurePerformanceTest.php ├── Service │ └── NumbersService.php └── Target │ └── Numbers.php ├── Stubs ├── Etc │ ├── Logger.php │ ├── MailQueue.php │ └── StackTrace.php └── Kernel │ └── EmptyKernel.php ├── Util.php └── media ├── avatar-HQ.png ├── avatar-wrong-format.jpg └── avatar.png /.editorconfig: -------------------------------------------------------------------------------- 1 | [composer.json] 2 | indent_size = 4 3 | 4 | [*.php] 5 | indent_size = 4 6 | ij_php_align_assignments = true 7 | ij_php_align_class_constants = true 8 | ij_php_align_enum_cases = true 9 | ij_php_align_key_value_pairs = true 10 | ij_php_align_multiline_parameters_in_calls = false 11 | ij_php_align_phpdoc_comments = true 12 | ij_php_align_phpdoc_param_names = true 13 | ij_php_blank_lines_before_package = 0 14 | ij_php_comma_after_last_argument = true 15 | ij_php_comma_after_last_array_element = true 16 | ij_php_comma_after_last_closure_use_var = true 17 | ij_php_comma_after_last_match_arm = true 18 | ij_php_comma_after_last_parameter = true 19 | ij_php_force_empty_methods_in_one_line = true 20 | ij_php_force_short_declaration_array_style = true 21 | ij_php_keep_rparen_and_lbrace_on_one_line = true 22 | ij_php_line_comment_add_space = true 23 | ij_php_line_comment_at_first_column = false 24 | ij_php_phpdoc_blank_lines_around_parameters = true 25 | ij_php_phpdoc_wrap_long_lines = true 26 | ij_php_space_before_short_closure_left_parenthesis = true 27 | 28 | [*.xml] 29 | indent_size = 2 30 | tab_width = 2 31 | ij_continuation_indent_size = 4 32 | 33 | [*.md] 34 | indent_size = 2 35 | -------------------------------------------------------------------------------- /.github/workflows/performance-tests.yml: -------------------------------------------------------------------------------- 1 | name: PHP Performance Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | run: 11 | runs-on: ${{ matrix.operating-system }} 12 | 13 | strategy: 14 | matrix: 15 | operating-system: [ ubuntu-latest ] 16 | php-version: [ '8.1', '8.2' ] 17 | 18 | name: PHP ${{ matrix.php-version }} performance test on ${{ matrix.operating-system }} 19 | 20 | steps: 21 | - name: Checkout Code 22 | uses: actions/checkout@v3 23 | 24 | - name: Install PHP 25 | id: php 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php-version }} 29 | coverage: none 30 | ini-values: opcache.enable_cli=1 31 | 32 | - name: Check PHP version 33 | run: php -v 34 | 35 | - name: Cache Composer packages 36 | id: composer-cache 37 | uses: actions/cache@v3 38 | with: 39 | path: vendor 40 | key: ${{ runner.os }}-${{ matrix.operating-system }}-php-${{ steps.php.outputs.php-version }}-composer-${{ hashFiles('**/composer.json') }} 41 | restore-keys: | 42 | ${{ runner.os }}-${{ matrix.operating-system }}-php-${{ steps.php.outputs.php-version }} 43 | 44 | - if: steps.composer-cache.outputs.cache-hit != 'true' 45 | name: Install Composer dependencies 46 | run: composer install --prefer-dist --no-progress 47 | 48 | - name: PHPUnit Performance Tests 49 | run: vendor/bin/phpunit --testsuite=Performance --display-notices 50 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: PHP Tests 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request_review: 7 | types: [ submitted, edited ] 8 | 9 | pull_request: 10 | types: 11 | - opened 12 | - edited 13 | branches: 14 | - master 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | run: 21 | runs-on: ${{ matrix.operating-system }} 22 | 23 | strategy: 24 | matrix: 25 | operating-system: [ ubuntu-latest ] 26 | php-version: [ '8.1', '8.2' ] 27 | 28 | name: PHP ${{ matrix.php-version }} tests on ${{ matrix.operating-system }} 29 | 30 | steps: 31 | - name: Checkout Code 32 | uses: actions/checkout@v3 33 | 34 | - name: Install PHP 35 | id: php 36 | uses: shivammathur/setup-php@v2 37 | with: 38 | php-version: ${{ matrix.php-version }} 39 | 40 | - name: Check PHP version 41 | run: php -v 42 | 43 | - name: Cache Composer packages 44 | id: composer-cache 45 | uses: actions/cache@v3 46 | with: 47 | path: vendor 48 | key: ${{ runner.os }}-${{ matrix.operating-system }}-php-${{ steps.php.outputs.php-version }}-composer-${{ hashFiles('**/composer.json') }} 49 | restore-keys: | 50 | ${{ runner.os }}-${{ matrix.operating-system }}-php-${{ steps.php.outputs.php-version }} 51 | 52 | - if: steps.composer-cache.outputs.cache-hit != 'true' 53 | name: Install Composer dependencies 54 | run: composer install --prefer-dist --no-progress 55 | 56 | - name: PHPUnit Tests 57 | run: vendor/bin/phpunit --testsuite=Tests --coverage-clover ./tests/coverage.xml --display-notices 58 | 59 | - name: Upload coverage reports to Codecov 60 | uses: codecov/codecov-action@v3 61 | with: 62 | token: ${{ secrets.CODECOV_TOKEN }} 63 | files: ./tests/coverage.xml 64 | flags: os-${{ matrix.operating-system }}_php-${{ matrix.php-version }} 65 | verbose: true 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs 2 | .idea/ 3 | 4 | # Composer 5 | vendor/ 6 | composer.lock 7 | 8 | # PHPUnit 9 | tests/coverage/ 10 | tests/cache/ 11 | .phpunit.result.cache 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Okapi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /XDEBUG.md: -------------------------------------------------------------------------------- 1 | # PhpStorm 2 | 3 | ## Linux 4 | 5 | Instructions: 6 | - Install `PHP` on your machine 7 | - Install `xdebug` on your machine (see https://xdebug.org/wizard) 8 | 9 | PhpStorm configuration: 10 | - `File > Settings` 11 | - `PHP > CLI Interpreter > ...`: 12 | - `+ > /usr/bin/php` 13 | - Configuration options: 14 | - xdebug.mode=debug 15 | - xdebug.client_host=YOUR_HOST_MACHINE_IP (e.g. 192.168.178.200) 16 | - `PHP > Debug`: 17 | - Uncheck `Break at first line in PHP scripts` 18 | - Uncheck `Force break at first line when no path mapping specified` 19 | - Uncheck `Force break at first line when a script is outside the project` 20 | - Run Configuration for Tests: 21 | - PhpUnit 22 | - Defined in the configuration file (`phpunit.xml`) 23 | - Test Runner options: `--coverage-html tests/coverage` 24 | - Preferred Coverage Engine: XDebug 25 | - Environment variables: `XDEBUG_MODE=debug,coverage` 26 | 27 | 28 | ## Windows 29 | 30 | Should be similar to Linux. 31 | 32 | 33 | # CLI 34 | 35 | ## Linux 36 | 37 | - `XDEBUG_MODE=debug,coverage composer run-script test-coverage` 38 | 39 | 40 | ## Windows 41 | 42 | Cmd: 43 | - `set XDEBUG_MODE=debug,coverage && composer run-script test-coverage` 44 | 45 | Powershell: 46 | - `$env:XDEBUG_MODE="debug,coverage"; composer run-script test-coverage` 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "okapi/aop", 3 | "description": "PHP AOP is a PHP library that provides a powerful Aspect Oriented Programming (AOP) implementation for PHP.", 4 | "type": "library", 5 | "homepage": "https://github.com/okapi-web/php-aop", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "WalterWoshid", 10 | "email": "wotschel.valentin@googlemail.com", 11 | "homepage": "https://github.com/WalterWoshid" 12 | } 13 | ], 14 | "keywords": [ 15 | "aop", 16 | "aspect-oriented-programming", 17 | "php", 18 | "php-aop" 19 | ], 20 | "scripts": { 21 | "test": "phpunit --testsuite=Tests --display-notices", 22 | "test-performance": "phpunit --testsuite=Performance --display-notices", 23 | "test-coverage": "phpunit --testsuite=Tests --coverage-html tests/coverage --display-notices" 24 | }, 25 | "require": { 26 | "php": ">=8.1", 27 | "nette/php-generator": "^4.0", 28 | "okapi/code-transformer": "1.3.7", 29 | "okapi/wildcards": "^1.0", 30 | "okapi/singleton": "^1.0", 31 | "php-di/php-di": "^7.0" 32 | }, 33 | "require-dev": { 34 | "phpunit/phpunit": "^10.3", 35 | "symfony/var-dumper": "^6.3", 36 | "symfony/console": "^6.3" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Okapi\\Aop\\": "src/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Okapi\\Aop\\Tests\\": "tests/" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Functional 10 | tests/Integration 11 | 12 | 13 | 14 | tests/Performance 15 | 16 | 17 | 18 | 19 | 20 | src/ 21 | 22 | 23 | src/Core/.phpstorm.meta.php 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Advice/AdviceType.php: -------------------------------------------------------------------------------- 1 | 15 | * It should be extended from to categorize the method advice types. 16 | * 17 | * @see Before 18 | * @see Around 19 | * @see After 20 | */ 21 | abstract class MethodAdvice extends BaseAdvice 22 | { 23 | public ?Regex $method; 24 | 25 | /** 26 | * MethodAdvice constructor. 27 | * 28 | * @param string|null $class Wildcard pattern for the class name. 29 | * @param string|null $method Wildcard pattern for the method name. 30 | * @param int $order The order of the advice. 31 | * @param bool $interceptTraitMethods If {@see true}, trait methods will be intercepted. 32 | * [Default: {@see true}] 33 | * @param bool $onlyPublicMethods If {@see true}, only public methods will be intercepted. 34 | * [Default: {@see false}] 35 | */ 36 | public function __construct( 37 | ?string $class = null, 38 | ?string $method = null, 39 | int $order = 0, 40 | public bool $interceptTraitMethods = true, 41 | public bool $onlyPublicMethods = false, 42 | ) { 43 | parent::__construct($class, $order); 44 | $this->method = $method ? Regex::fromWildcard($method) : null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Core/Attributes/Base/BaseAdvice.php: -------------------------------------------------------------------------------- 1 | 12 | * It should be extended from to categorize the advice types. 13 | * 14 | * @see MethodAdvice 15 | */ 16 | abstract class BaseAdvice extends BaseAttribute 17 | { 18 | public ?Regex $class; 19 | 20 | /** 21 | * Base advice constructor. 22 | * 23 | * @param string|null $class Wildcard pattern for the class name. 24 | * @param int $order The order of the advice. 25 | */ 26 | public function __construct( 27 | ?string $class = null, 28 | public int $order = 0, 29 | ) { 30 | $this->class = $class ? Regex::fromWildcard($class) : null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Core/Attributes/Base/BaseAttribute.php: -------------------------------------------------------------------------------- 1 | appendToCachePath($filePath, $this->proxyDir); 52 | } 53 | 54 | /** 55 | * Get the file path for a woven file. 56 | * 57 | * @param string $filePath 58 | * 59 | * @return string 60 | */ 61 | public function getWovenCachePath(string $filePath): string 62 | { 63 | return $this->appendToCachePath($filePath, $this->wovenDir); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Core/Cache/CacheStateFactory.php: -------------------------------------------------------------------------------- 1 | WovenCacheState::class, 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /src/Core/Cache/CacheStateManager.php: -------------------------------------------------------------------------------- 1 | aspectManager->getAspectAdviceNames(); 31 | $aspectHash = md5(serialize($aspectAdviceNames)); 32 | 33 | return $transformerHash . $aspectHash; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Core/Container/AdviceContainer.php: -------------------------------------------------------------------------------- 1 | newInstance(); 44 | 45 | // Check if the aspect are implicit or class/method-level explicit 46 | $isExplicit = (bool)$aspectRefClass->getAttributes(Attribute::class); 47 | 48 | if ($adviceAttributeInstance instanceof MethodAdvice) { 49 | $methodAdviceContainer = DI::make(MethodAdviceContainer::class, [ 50 | 'aspectClassName' => $aspectClassName, 51 | 'aspectInstance' => $aspectInstance, 52 | 'aspectRefClass' => $aspectRefClass, 53 | 'adviceAttribute' => $adviceAttribute, 54 | 'adviceAttributeInstance' => $adviceAttributeInstance, 55 | 'adviceRefMethod' => $adviceRefMethod, 56 | 'isExplicit' => $isExplicit, 57 | ]); 58 | 59 | // If the aspect is implicit, 60 | // check if the class and method names are set 61 | if (!$isExplicit) { 62 | if (!$adviceAttributeInstance->class) { 63 | throw new MissingClassNameException( 64 | $methodAdviceContainer->getName(), 65 | ); 66 | } 67 | 68 | if (!$adviceAttributeInstance->method) { 69 | throw new MissingMethodNameException( 70 | $methodAdviceContainer->getName(), 71 | ); 72 | } 73 | } 74 | 75 | return $methodAdviceContainer; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Core/Container/AdviceType/MethodAdviceContainer.php: -------------------------------------------------------------------------------- 1 | matchedMethods[] = DI::make(MatchedMethod::class, [ 70 | 'matchedRefMethod' => $matchedRefMethod, 71 | ]); 72 | } 73 | 74 | /** 75 | * Get matched methods. 76 | * 77 | * @return MatchedMethod[] 78 | */ 79 | public function getMatchedMethods(): array 80 | { 81 | return $this->matchedMethods; 82 | } 83 | 84 | /** 85 | * Get advice name. 86 | * 87 | * @return string 88 | */ 89 | public function getName(): string 90 | { 91 | return $this->aspectClassName . '::' . $this->adviceRefMethod->getName(); 92 | } 93 | 94 | /** 95 | * Is explicit. 96 | * 97 | * @return bool 98 | */ 99 | public function isExplicit(): bool 100 | { 101 | return $this->isExplicit; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Core/Container/JoinPoint/MethodJoinPointContainer.php: -------------------------------------------------------------------------------- 1 | interceptor = DI::make(Interceptor::class, [ 26 | 'className' => $className, 27 | 'methodName' => $methodName, 28 | 'joinPoints' => $joinPoints, 29 | ]); 30 | } 31 | 32 | // TODO: docs 33 | public function getValue(): array 34 | { 35 | return [ 36 | $this->interceptor, 37 | Interceptor::METHOD_NAME, 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Core/Container/JoinPointContainer.php: -------------------------------------------------------------------------------- 1 | > $joinPointPropertyValue 27 | */ 28 | public function __construct( 29 | string $className, 30 | array $joinPointPropertyValue, 31 | ) { 32 | foreach ($joinPointPropertyValue as $joinPointType => $joinPointValue) { 33 | if ($joinPointType === JoinPoint::TYPE_METHOD) { 34 | foreach ($joinPointValue as $methodName => $joinPoints) { 35 | $this->methodJoinPointContainers[] = DI::make( 36 | MethodJoinPointContainer::class, 37 | [ 38 | 'className' => $className, 39 | 'methodName' => $methodName, 40 | 'joinPoints' => $joinPoints, 41 | ], 42 | ); 43 | } 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * Get the value of the join point container. 50 | * 51 | * @return array<'method', array> 52 | */ 53 | public function getValue(): array 54 | { 55 | $value = []; 56 | 57 | foreach ($this->methodJoinPointContainers as $methodJoinPointContainer) { 58 | $methodName = $methodJoinPointContainer->methodName; 59 | $joinPointValue = $methodJoinPointContainer->getValue(); 60 | 61 | $value[JoinPoint::TYPE_METHOD][$methodName] = $joinPointValue; 62 | } 63 | 64 | return $value; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Core/Container/TransformerManager.php: -------------------------------------------------------------------------------- 1 | adviceAttributeInstance instanceof Before: 45 | return DI::make(BeforeMethodInvocation::class, [ 46 | 'subject' => $subject, 47 | 'className' => $className, 48 | 'methodName' => $methodName, 49 | 'result' => $result, 50 | 'arguments' => &$arguments, 51 | ]); 52 | 53 | // Around 54 | case $adviceContainer->adviceAttributeInstance instanceof Around: 55 | return DI::make(AroundMethodInvocation::class, [ 56 | 'subject' => $subject, 57 | 'className' => $className, 58 | 'methodName' => $methodName, 59 | 'result' => $result, 60 | 'arguments' => &$arguments, 61 | ]); 62 | 63 | // After 64 | case $adviceContainer->adviceAttributeInstance instanceof After: 65 | return DI::make(AfterMethodInvocation::class, [ 66 | 'subject' => $subject, 67 | 'className' => $className, 68 | 'methodName' => $methodName, 69 | 'result' => $result, 70 | 'arguments' => &$arguments, 71 | ]); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Core/Invocation/AdviceChainAwareTrait.php: -------------------------------------------------------------------------------- 1 | adviceChain = $adviceChain; 26 | } 27 | 28 | /** 29 | * Call next advice or target method. 30 | * 31 | * @param bool $allowRepeatedCalls 32 | *
If {@see true}, the original method will be called again.
33 | * If {@see false}, the original method will be called only once and every 34 | * subsequent call will return the same result.
35 | * Default: {@see false}
36 | * WARNING: May cause unexpected behavior and side effects. 37 | * 38 | * @return mixed 39 | */ 40 | public function proceed(bool $allowRepeatedCalls = false): mixed 41 | { 42 | return $this->adviceChain->proceed($allowRepeatedCalls); 43 | } 44 | 45 | /** 46 | * Set result. 47 | * 48 | * @param mixed $result 49 | * 50 | * @return void 51 | */ 52 | public function setResult(mixed $result): void 53 | { 54 | $this->adviceChain->setResult($result); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Core/JoinPoint/JoinPoint.php: -------------------------------------------------------------------------------- 1 | joinPoints as $joinPoint) { 51 | $adviceContainers = $this->aspectMatcher->getMatchedAdviceContainersByJoinPoint( 52 | $this->className, 53 | $joinPoint, 54 | ); 55 | 56 | foreach ($adviceContainers as $adviceContainer) { 57 | $adviceAttributeInstance = $adviceContainer->adviceAttributeInstance; 58 | switch (true) { 59 | case $adviceAttributeInstance instanceof Before: 60 | $beforeInterceptors[] = $adviceContainer; 61 | break; 62 | case $adviceAttributeInstance instanceof Around: 63 | $aroundInterceptors[] = $adviceContainer; 64 | break; 65 | case $adviceAttributeInstance instanceof After: 66 | $afterInterceptors[] = $adviceContainer; 67 | break; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Core/JoinPoint/JoinPointInjector.php: -------------------------------------------------------------------------------- 1 | getStaticPropertyValue( 34 | JoinPoint::JOIN_POINTS_PARAMETER_NAME, 35 | ); 36 | 37 | // Convert to JoinPointContainer 38 | $joinPointContainer = DI::make(JoinPointContainer::class, [ 39 | 'className' => $className, 40 | 'joinPointPropertyValue' => $staticPropertyValue, 41 | ]); 42 | 43 | // Set the join points 44 | $refClass->setStaticPropertyValue( 45 | JoinPoint::JOIN_POINTS_PARAMETER_NAME, 46 | $joinPointContainer->getValue(), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Core/Matcher/AdviceMatcher.php: -------------------------------------------------------------------------------- 1 | methodMatcher->match( 44 | $adviceContainer, 45 | $refClass, 46 | ); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Core/Matcher/AdviceMatcher/MatchedMethod.php: -------------------------------------------------------------------------------- 1 | classLoader)) { 20 | $this->findClassLoader(); 21 | } 22 | 23 | return $this->classLoader->findFile($class); 24 | } 25 | 26 | private function findOriginalClassMock(string $class): string 27 | { 28 | if (!isset($this->classLoader)) { 29 | $this->findClassLoader(); 30 | } 31 | 32 | $original = new ReflectionProperty(ClassLoader::class, 'originalClassLoader'); 33 | $original = $original->getValue($this->classLoader); 34 | return $original->findFile($class); 35 | } 36 | 37 | private function findClassLoader(): void 38 | { 39 | foreach (spl_autoload_functions() as $function) { 40 | if (is_array($function) && $function[0] instanceof ClassLoader) { 41 | $this->classLoader = $function[0]; 42 | break; 43 | } 44 | } 45 | } 46 | 47 | public function assertWillBeWoven(string $className): void 48 | { 49 | $originalFilePath = Path::resolve($this->findOriginalClassMock($className)); 50 | 51 | $wovenPath = 52 | FilterInjector::PHP_FILTER_READ . 53 | StreamFilter::FILTER_ID . '/resource=' . 54 | $originalFilePath; 55 | 56 | $filePathMock = $this->findClassMock($className); 57 | 58 | Assert::assertEquals( 59 | $wovenPath, 60 | $filePathMock, 61 | "$className will not be woven", 62 | ); 63 | } 64 | 65 | public function assertAspectLoadedFromCache(string $className): void 66 | { 67 | $filePath = Path::resolve($this->findOriginalClassMock($className)); 68 | 69 | $cachePath = 70 | FilterInjector::PHP_FILTER_READ . 71 | CachedStreamFilter::CACHED_FILTER_ID . '/resource=' . 72 | $filePath; 73 | 74 | $filePathMock = $this->findClassMock($className); 75 | 76 | Assert::assertEquals( 77 | $cachePath, 78 | $filePathMock, 79 | "$className will not be loaded from cache", 80 | ); 81 | } 82 | 83 | public function assertAspectNotApplied(string $className): void 84 | { 85 | $originalFilePath = Path::resolve($this->findOriginalClassMock($className)); 86 | $filePathMock = $this->findClassMock($className); 87 | 88 | Assert::assertEquals( 89 | $originalFilePath, 90 | $filePathMock, 91 | "$className will be woven", 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/AdviceMatchingAbstractMethodTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(LocalFileUploader::class); 26 | 27 | $uploader = new LocalFileUploader(); 28 | 29 | $result = $uploader->upload('C:\Windows\Temp\file.txt'); 30 | 31 | /** @noinspection PhpConditionAlreadyCheckedInspection */ 32 | $this->assertEquals( 33 | 'C:/Windows/Temp/file.txt', 34 | $result 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/Aspect/FileUploaderAspect.php: -------------------------------------------------------------------------------- 1 | proceed(); 20 | $modifiedResult = str_replace('\\', '/', $result); 21 | $invocation->setResult($modifiedResult); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/AdviceMatchingAbstractMethod/Kernel.php: -------------------------------------------------------------------------------- 1 | getMethodName(); 19 | 20 | $logMessage = sprintf( 21 | "Method '%s' executed.", 22 | $methodName, 23 | ); 24 | 25 | $logger = Logger::getInstance(); 26 | $logger->log($logMessage); 27 | } 28 | 29 | #[Before( 30 | method: 'updateInventory', 31 | )] 32 | public function logUpdateInventory(BeforeMethodInvocation $invocation): void 33 | { 34 | $methodName = $invocation->getMethodName(); 35 | 36 | $logMessage = sprintf( 37 | "Method '%s' executed.", 38 | $methodName, 39 | ); 40 | 41 | $logger = Logger::getInstance(); 42 | $logger->log($logMessage); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/ExplicitClassLevelAspect/ExplicitClassLevelAspectTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(InventoryTracker::class); 25 | 26 | $this->executeTest(); 27 | } 28 | 29 | public function testCachedExplicitClassLevelAspect(): void 30 | { 31 | EmptyKernel::init(); 32 | 33 | $this->assertAspectLoadedFromCache(InventoryTracker::class); 34 | 35 | $this->executeTest(); 36 | } 37 | 38 | /** 39 | * @see LoggingAspect::logAllMethods() 40 | * @see LoggingAspect::logUpdateInventory() 41 | */ 42 | private function executeTest(): void 43 | { 44 | $inventoryTracker = new InventoryTracker(); 45 | $inventoryTracker->updateInventory(1, 100); 46 | $inventoryTracker->updateInventory(2, 200); 47 | 48 | $this->assertEquals(100, $inventoryTracker->checkInventory(1)); 49 | $this->assertEquals(200, $inventoryTracker->checkInventory(2)); 50 | 51 | $logger = Logger::getInstance(); 52 | 53 | $logs = $logger->getLogs(); 54 | $this->assertCount(6, $logs); 55 | 56 | $updateInventoryExecuted = 0; 57 | $checkInventoryExecuted = 0; 58 | $updateInventoryLog = "Method 'updateInventory' executed."; 59 | $checkInventoryLog = "Method 'checkInventory' executed."; 60 | 61 | foreach ($logs as $log) { 62 | if ($log === $updateInventoryLog) { 63 | $updateInventoryExecuted++; 64 | } elseif ($log === $checkInventoryLog) { 65 | $checkInventoryExecuted++; 66 | } 67 | } 68 | 69 | $this->assertEquals(4, $updateInventoryExecuted); 70 | $this->assertEquals(2, $checkInventoryExecuted); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/ExplicitClassLevelAspect/Target/InventoryTracker.php: -------------------------------------------------------------------------------- 1 | inventory[$productId] = $quantity; 15 | } 16 | 17 | public function checkInventory(int $productId): int 18 | { 19 | return $this->inventory[$productId] ?? 0; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/ExplicitMethodLevelAspect/Aspect/PerformanceAspect.php: -------------------------------------------------------------------------------- 1 | proceed(); 20 | $end = microtime(true); 21 | 22 | $executionTime = $end - $start; 23 | 24 | $class = $invocation->getClassName(); 25 | $method = $invocation->getMethodName(); 26 | 27 | $logMessage = sprintf( 28 | "Method %s::%s executed in %.2f seconds.", 29 | $class, 30 | $method, 31 | $executionTime, 32 | ); 33 | 34 | $logger = Logger::getInstance(); 35 | $logger->log($logMessage); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/ExplicitMethodLevelAspect/ExplicitMethodLevelAspectTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(CustomerService::class); 26 | 27 | $this->executeTest(); 28 | } 29 | 30 | public function testCachedNotRegisteredExplicitMethodLevelAspect(): void 31 | { 32 | EmptyKernel::init(); 33 | 34 | $this->assertAspectLoadedFromCache(CustomerService::class); 35 | 36 | $this->executeTest(); 37 | } 38 | 39 | public function testRegisteredExplicitMethodLevelAspect(): void 40 | { 41 | Util::clearCache(); 42 | Kernel::init(); 43 | 44 | $this->assertWillBeWoven(CustomerService::class); 45 | 46 | $this->executeTest(); 47 | } 48 | 49 | public function testCachedExplicitMethodLevelAspect(): void 50 | { 51 | Kernel::init(); 52 | 53 | $this->assertAspectLoadedFromCache(CustomerService::class); 54 | 55 | $this->executeTest(); 56 | } 57 | 58 | /** 59 | * @see PerformanceAspect::measure() 60 | */ 61 | private function executeTest(): void 62 | { 63 | $customerService = new CustomerService(); 64 | $customerService->createCustomer(); 65 | 66 | $logger = Logger::getInstance(); 67 | 68 | $logs = $logger->getLogs(); 69 | $this->assertCount(1, $logs); 70 | 71 | $firstLog = $logs[0]; 72 | $wildcard = 'Method *::* executed in * seconds.'; 73 | $regex = Regex::fromWildcard($wildcard); 74 | $matches = $regex->matches($firstLog); 75 | $this->assertTrue($matches); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/ExplicitMethodLevelAspect/Kernel.php: -------------------------------------------------------------------------------- 1 | getArguments(); 20 | 21 | $firstArgument = reset($arguments); 22 | $firstArgumentKey = key($arguments); 23 | 24 | if (gettype($firstArgument) === 'array') { 25 | $id = &$firstArgument['id']; 26 | $id .= self::SECRET_HASH; 27 | 28 | $arguments[$firstArgumentKey] = $firstArgument; 29 | 30 | $invocation->setArguments($arguments); 31 | } 32 | 33 | if (gettype($firstArgument) === 'string') { 34 | $id = &$firstArgument; 35 | $id .= self::SECRET_HASH; 36 | 37 | $arguments[$firstArgumentKey] = $id; 38 | 39 | $invocation->setArguments($arguments); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/MultipleExplicitMethodLevelAspects/MultipleExplicitMethodLevelAspectsTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(AccountService::class); 30 | $accountService = new AccountService(); 31 | 32 | $accountService->createAccount(['id' => $id]); 33 | 34 | $accounts = $accountService->getAccounts(); 35 | $this->assertCount(1, $accounts); 36 | 37 | $firstAccount = $accounts[0]; 38 | $this->assertStringEndsWith(SecurityAspect::SECRET_HASH, $firstAccount); 39 | 40 | /** @noinspection PhpUnhandledExceptionInspection */ 41 | $accountService->deleteAccount($id); 42 | 43 | $accounts = $accountService->getAccounts(); 44 | $this->assertCount(0, $accounts); 45 | 46 | 47 | $this->assertWillBeWoven(TransactionService::class); 48 | $transactionService = new TransactionService(); 49 | 50 | $transactionService->createTransaction(['id' => $id]); 51 | 52 | $transactions = $transactionService->getTransactions(); 53 | $this->assertCount(1, $transactions); 54 | 55 | $firstTransaction = $transactions[0]; 56 | $this->assertStringEndsWith(SecurityAspect::SECRET_HASH, $firstTransaction); 57 | 58 | /** @noinspection PhpUnhandledExceptionInspection */ 59 | $transactionService->rollbackTransaction($id); 60 | 61 | $transactions = $transactionService->getTransactions(); 62 | $this->assertCount(0, $transactions); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/MultipleExplicitMethodLevelAspects/Target/AccountService.php: -------------------------------------------------------------------------------- 1 | accounts[] = $userData['id']; 16 | } 17 | 18 | #[SecurityAspect] 19 | public function deleteAccount(string $accountId): void 20 | { 21 | $accountIndex = array_search($accountId, $this->accounts); 22 | 23 | if ($accountIndex === false) { 24 | /** @noinspection PhpUnhandledExceptionInspection */ 25 | throw new Exception("Account with id $accountId not found."); 26 | } 27 | 28 | unset($this->accounts[$accountIndex]); 29 | } 30 | 31 | public function getAccounts(): array 32 | { 33 | return $this->accounts; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Functional/AdviceApplication/MultipleExplicitMethodLevelAspects/Target/TransactionService.php: -------------------------------------------------------------------------------- 1 | transactions[] = $transactionData['id']; 16 | } 17 | 18 | #[SecurityAspect] 19 | public function rollbackTransaction(string $transactionId): void 20 | { 21 | $transactionIndex = array_search($transactionId, $this->transactions); 22 | 23 | if ($transactionIndex === false) { 24 | /** @noinspection PhpUnhandledExceptionInspection */ 25 | throw new Exception("Transaction with id $transactionId not found."); 26 | } 27 | 28 | unset($this->transactions[$transactionIndex]); 29 | } 30 | 31 | public function getTransactions(): array 32 | { 33 | return $this->transactions; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/AdviceOrder/AdviceOrderTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(ArticleManager::class); 29 | $articleManager = new ArticleManager(); 30 | 31 | $articleManager->createArticle( 32 | 'Hello World', 33 | 'AOP is awesome!', 34 | ); 35 | 36 | $stackTrace = StackTrace::getInstance(); 37 | 38 | $this->assertEquals( 39 | [ 40 | 'checkForSpam', 41 | 'validateContent', 42 | 'ensureProperFormatting', 43 | ], 44 | $stackTrace->getStackTrace(), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/AdviceOrder/Aspect/ArticleModerationAspect.php: -------------------------------------------------------------------------------- 1 | addTrace('validateContent'); 23 | } 24 | 25 | #[After( 26 | class: ArticleManager::class, 27 | method: 'createArticle', 28 | order: -10, 29 | )] 30 | public function checkForSpam() 31 | { 32 | $stackTrace = StackTrace::getInstance(); 33 | $stackTrace->addTrace('checkForSpam'); 34 | } 35 | 36 | #[After( 37 | class: ArticleManager::class, 38 | method: 'createArticle', 39 | order: 10, 40 | )] 41 | public function ensureProperFormatting() 42 | { 43 | $stackTrace = StackTrace::getInstance(); 44 | $stackTrace->addTrace('ensureProperFormatting'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/AdviceOrder/Kernel.php: -------------------------------------------------------------------------------- 1 | getAdviceType(); 33 | $logger = Logger::getInstance(); 34 | 35 | if ($adviceType === AdviceType::Before) { 36 | $message = 'Starting calculation...'; 37 | $logger->log($message); 38 | } 39 | 40 | if ($adviceType === AdviceType::Around) { 41 | assert($invocation instanceof AroundMethodInvocation); 42 | 43 | $startTime = microtime(true); 44 | $invocation->proceed(); 45 | $endTime = microtime(true); 46 | $elapsedTime = $endTime - $startTime; 47 | 48 | $message = sprintf('Calculation took %.2f seconds', $elapsedTime); 49 | $logger->log($message); 50 | } 51 | 52 | if ($adviceType === AdviceType::After) { 53 | $result = $invocation->proceed(); 54 | 55 | $message = sprintf('Calculation result: %d', $result); 56 | $logger->log($message); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameAdviceMethod/BeforeAroundAfterAdviceOnSameAdviceMethodTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(Calculator::class); 28 | $calculator = new Calculator(); 29 | 30 | $result = $calculator->add(2, 3); 31 | $this->assertSame(5, $result); 32 | 33 | $logger = Logger::getInstance(); 34 | 35 | $logs = $logger->getLogs(); 36 | $this->assertCount(3, $logs); 37 | 38 | $log1 = $logs[0]; 39 | $this->assertSame('Starting calculation...', $log1); 40 | 41 | $log2 = $logs[1]; 42 | $wildcard = 'Calculation took * seconds'; 43 | $regex = Regex::fromWildcard($wildcard); 44 | $matches = $regex->matches($log2); 45 | $this->assertTrue($matches); 46 | 47 | $log3 = $logs[2]; 48 | $this->assertSame('Calculation result: 5', $log3); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameAdviceMethod/Kernel.php: -------------------------------------------------------------------------------- 1 | getArgument('amount'); 27 | 28 | if ($amount < 0) { 29 | throw new InvalidArgumentException('Invalid payment amount'); 30 | } 31 | } 32 | 33 | #[Around( 34 | class: PaymentProcessor::class, 35 | method: 'processPayment', 36 | )] 37 | public function logPayment(AroundMethodInvocation $invocation): void 38 | { 39 | $startTime = microtime(true); 40 | 41 | $invocation->proceed(); 42 | 43 | $endTime = microtime(true); 44 | $elapsedTime = $endTime - $startTime; 45 | 46 | $amount = $invocation->getArgument('amount'); 47 | 48 | $logMessage = sprintf( 49 | 'Payment processed for amount $%.2f in %.2f seconds', 50 | $amount, 51 | $elapsedTime, 52 | ); 53 | 54 | $logger = Logger::getInstance(); 55 | $logger->log($logMessage); 56 | } 57 | 58 | #[After( 59 | class: PaymentProcessor::class, 60 | method: 'processPayment', 61 | )] 62 | public function sendEmailNotification(AfterMethodInvocation $invocation): void 63 | { 64 | $result = $invocation->proceed(); 65 | $amount = $invocation->getArgument('amount'); 66 | 67 | $message = sprintf( 68 | 'Payment processed for amount $%.2f', 69 | $amount, 70 | ); 71 | if ($result === true) { 72 | $message .= ' - Payment successful'; 73 | } else { 74 | $message .= ' - Payment failed'; 75 | } 76 | 77 | $mailQueue = MailQueue::getInstance(); 78 | $mailQueue->addMail($message); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameTargetMethod/BeforeAroundAfterAdviceOnSameTargetMethodTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(PaymentProcessor::class); 32 | $processor = new PaymentProcessor(); 33 | 34 | // Test with an invalid payment amount 35 | $amount = -50.00; 36 | $exceptionThrown = false; 37 | try { 38 | $processor->processPayment($amount); 39 | } catch (InvalidArgumentException $e) { 40 | $exceptionThrown = true; 41 | $this->assertSame( 42 | 'Invalid payment amount', 43 | $e->getMessage(), 44 | ); 45 | } 46 | $this->assertTrue($exceptionThrown); 47 | 48 | // Test with a valid payment amount 49 | $amount = 420.00; 50 | $success = $processor->processPayment($amount); 51 | $this->assertTrue($success); 52 | 53 | // Test that the log message was printed 54 | $logger = Logger::getInstance(); 55 | $logs = $logger->getLogs(); 56 | $this->assertCount(1, $logs); 57 | $logMessage = $logs[0]; 58 | $wildcard = 'Payment processed for amount $* in * seconds'; 59 | $regex = Regex::fromWildcard($wildcard); 60 | $matches = $regex->matches($logMessage); 61 | $this->assertTrue($matches); 62 | 63 | // Test that the email notification was sent 64 | $mailQueue = MailQueue::getInstance(); 65 | $mails = $mailQueue->getMails(); 66 | $this->assertCount(1, $mails); 67 | $mail = $mails[0]; 68 | $wildcard = 'Payment processed for amount $* - Payment successful'; 69 | $regex = Regex::fromWildcard($wildcard); 70 | $matches = $regex->matches($mail); 71 | $this->assertTrue($matches); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/BeforeAroundAfterAdviceOnSameTargetMethod/Kernel.php: -------------------------------------------------------------------------------- 1 | getClassName() === SmsSender::class) { 22 | throw new Error('SmsSender should not be intercepted.'); 23 | } 24 | 25 | return $invocation->proceed(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ClassHierarchyAspect/ClassHierarchyAspectTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(EmailSender::class); 27 | $emailSender = new EmailSender(); 28 | 29 | $recipient = 'test@test.com'; 30 | $subject = 'Test'; 31 | $body = 'Test'; 32 | $result = $emailSender->send($recipient, $subject, $body); 33 | 34 | $this->assertTrue($result); 35 | 36 | 37 | $this->assertAspectNotApplied(SmsSender::class); 38 | $smsSender = new SmsSender(); 39 | 40 | $recipient = '123456789'; 41 | $message = 'Test'; 42 | $result = $smsSender->send($recipient, $message); 43 | 44 | // Should not throw an error 45 | $this->assertTrue($result); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ClassHierarchyAspect/Kernel.php: -------------------------------------------------------------------------------- 1 | getArgument('comment'); 23 | 24 | $inappropriateWords = ['bad', 'terrible', 'awful']; 25 | 26 | foreach ($inappropriateWords as $word) { 27 | if (str_contains($comment, $word)) { 28 | throw new Exception('Comment contains inappropriate language!'); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ExceptionInsideAdvice/ExceptionInsideAdviceTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(CommentController::class); 27 | $commentController = new CommentController(); 28 | 29 | $commentController->saveComment('This is a good comment'); 30 | $this->assertTrue(true); 31 | 32 | $this->expectException(Exception::class); 33 | $this->expectExceptionMessage('Comment contains inappropriate language!'); 34 | $commentController->saveComment('This is a bad comment'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ExceptionInsideAdvice/Kernel.php: -------------------------------------------------------------------------------- 1 | getSubject(); 21 | 22 | $subject->data = [ 23 | 'd' => 4, 24 | 'e' => 5, 25 | 'f' => 6, 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/Include/Database/data.php: -------------------------------------------------------------------------------- 1 | 1, 5 | 'b' => 2, 6 | 'c' => 3, 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/Include/IncludeTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(SecureDatabaseService::class); 26 | 27 | $service = new SecureDatabaseService(); 28 | $service->load(); 29 | 30 | $data = $service->getData(); 31 | 32 | $this->assertEquals( 33 | [ 34 | 'd' => 4, 35 | 'e' => 5, 36 | 'f' => 6, 37 | ], 38 | $data, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/Include/Kernel.php: -------------------------------------------------------------------------------- 1 | data === null) { 12 | $this->data = require dirname(__DIR__, 3) . '/AdviceBehavior/Include/Database/data.php'; 13 | } 14 | 15 | return $this; 16 | } 17 | 18 | public function getData(): array 19 | { 20 | if ($this->data === null) { 21 | $this->load(); 22 | } 23 | 24 | return $this->data; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/InterfaceAdvice/Aspect/UserInterfaceAspect.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(User::class); 26 | $user = new User(); 27 | $userName = $user->getName(); 28 | 29 | $this->assertSame('Jane Doe', $userName); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/InterfaceAdvice/Kernel.php: -------------------------------------------------------------------------------- 1 | __DIR__, 11 | 'file' => __FILE__, 12 | 'function' => __FUNCTION__, 13 | 'class' => __CLASS__, 14 | 'trait' => __TRAIT__, 15 | 'method' => __METHOD__, 16 | 'namespace' => __NAMESPACE__, 17 | 'targetClassClass' => TargetClass::class, 18 | 'targetTraitClass' => TargetTrait::class, 19 | 'targetParentClass' => TargetParent::class, 20 | 'selfClass' => self::class, 21 | ]; 22 | 23 | public array $property = [ 24 | 'dir' => __DIR__, 25 | 'file' => __FILE__, 26 | 'function' => __FUNCTION__, 27 | 'class' => __CLASS__, 28 | 'trait' => __TRAIT__, 29 | 'method' => __METHOD__, 30 | 'namespace' => __NAMESPACE__, 31 | 'targetClassClass' => TargetClass::class, 32 | 'targetTraitClass' => TargetTrait::class, 33 | 'targetParentClass' => TargetParent::class, 34 | 'selfClass' => self::class, 35 | ]; 36 | 37 | public static array $staticProperty = [ 38 | 'dir' => __DIR__, 39 | 'file' => __FILE__, 40 | 'function' => __FUNCTION__, 41 | 'class' => __CLASS__, 42 | 'trait' => __TRAIT__, 43 | 'method' => __METHOD__, 44 | 'namespace' => __NAMESPACE__, 45 | 'targetClassClass' => TargetClass::class, 46 | 'targetTraitClass' => TargetTrait::class, 47 | 'targetParentClass' => TargetParent::class, 48 | 'selfClass' => self::class, 49 | ]; 50 | 51 | public function method(): array 52 | { 53 | return [ 54 | 'dir' => __DIR__, 55 | 'file' => __FILE__, 56 | 'function' => __FUNCTION__, 57 | 'class' => __CLASS__, 58 | 'trait' => __TRAIT__, 59 | 'method' => __METHOD__, 60 | 'namespace' => __NAMESPACE__, 61 | 'targetClassClass' => TargetClass::class, 62 | 'targetTraitClass' => TargetTrait::class, 63 | 'targetParentClass' => TargetParent::class, 64 | 'selfClass' => self::class, 65 | 'staticClass' => static::class, 66 | ]; 67 | } 68 | 69 | public static function staticMethod(): array 70 | { 71 | return [ 72 | 'dir' => __DIR__, 73 | 'file' => __FILE__, 74 | 'function' => __FUNCTION__, 75 | 'class' => __CLASS__, 76 | 'trait' => __TRAIT__, 77 | 'method' => __METHOD__, 78 | 'namespace' => __NAMESPACE__, 79 | 'targetClassClass' => TargetClass::class, 80 | 'targetTraitClass' => TargetTrait::class, 81 | 'targetParentClass' => TargetParent::class, 82 | 'selfClass' => self::class, 83 | 'staticClass' => static::class, 84 | ]; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/MagicConstants/Target/TargetParent.php: -------------------------------------------------------------------------------- 1 | __DIR__, 9 | 'file' => __FILE__, 10 | 'function' => __FUNCTION__, 11 | 'class' => __CLASS__, 12 | 'trait' => __TRAIT__, 13 | 'method' => __METHOD__, 14 | 'namespace' => __NAMESPACE__, 15 | 'targetClassClass' => TargetClass::class, 16 | 'targetTraitClass' => TargetTrait::class, 17 | 'targetParentClass' => TargetParent::class, 18 | 'selfClass' => self::class, 19 | ]; 20 | 21 | public array $parentProperty = [ 22 | 'dir' => __DIR__, 23 | 'file' => __FILE__, 24 | 'function' => __FUNCTION__, 25 | 'class' => __CLASS__, 26 | 'trait' => __TRAIT__, 27 | 'method' => __METHOD__, 28 | 'namespace' => __NAMESPACE__, 29 | 'targetClassClass' => TargetClass::class, 30 | 'targetTraitClass' => TargetTrait::class, 31 | 'targetParentClass' => TargetParent::class, 32 | 'selfClass' => self::class, 33 | ]; 34 | 35 | public static array $parentStaticProperty = [ 36 | 'dir' => __DIR__, 37 | 'file' => __FILE__, 38 | 'function' => __FUNCTION__, 39 | 'class' => __CLASS__, 40 | 'trait' => __TRAIT__, 41 | 'method' => __METHOD__, 42 | 'namespace' => __NAMESPACE__, 43 | 'targetClassClass' => TargetClass::class, 44 | 'targetTraitClass' => TargetTrait::class, 45 | 'targetParentClass' => TargetParent::class, 46 | 'selfClass' => self::class, 47 | ]; 48 | 49 | public function parentMethod(): array 50 | { 51 | return [ 52 | 'dir' => __DIR__, 53 | 'file' => __FILE__, 54 | 'function' => __FUNCTION__, 55 | 'class' => __CLASS__, 56 | 'trait' => __TRAIT__, 57 | 'method' => __METHOD__, 58 | 'namespace' => __NAMESPACE__, 59 | 'targetClassClass' => TargetClass::class, 60 | 'targetTraitClass' => TargetTrait::class, 61 | 'targetParentClass' => TargetParent::class, 62 | 'selfClass' => self::class, 63 | 'staticClass' => static::class, 64 | ]; 65 | } 66 | 67 | public static function parentStaticMethod(): array 68 | { 69 | return [ 70 | 'dir' => __DIR__, 71 | 'file' => __FILE__, 72 | 'function' => __FUNCTION__, 73 | 'class' => __CLASS__, 74 | 'trait' => __TRAIT__, 75 | 'method' => __METHOD__, 76 | 'namespace' => __NAMESPACE__, 77 | 'targetClassClass' => TargetClass::class, 78 | 'targetTraitClass' => TargetTrait::class, 79 | 'targetParentClass' => TargetParent::class, 80 | 'selfClass' => self::class, 81 | 'staticClass' => static::class, 82 | ]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/MagicConstants/Target/TargetParent82.php: -------------------------------------------------------------------------------- 1 | __DIR__, 9 | 'file' => __FILE__, 10 | 'function' => __FUNCTION__, 11 | 'class' => __CLASS__, 12 | 'trait' => __TRAIT__, 13 | 'method' => __METHOD__, 14 | 'namespace' => __NAMESPACE__, 15 | 'targetClassClass' => TargetClass82::class, 16 | 'targetTraitClass' => TargetTrait82::class, 17 | 'targetParentClass' => TargetParent82::class, 18 | 'selfClass' => self::class, 19 | ]; 20 | 21 | public array $parentProperty = [ 22 | 'dir' => __DIR__, 23 | 'file' => __FILE__, 24 | 'function' => __FUNCTION__, 25 | 'class' => __CLASS__, 26 | 'trait' => __TRAIT__, 27 | 'method' => __METHOD__, 28 | 'namespace' => __NAMESPACE__, 29 | 'targetClassClass' => TargetClass82::class, 30 | 'targetTraitClass' => TargetTrait82::class, 31 | 'targetParentClass' => TargetParent82::class, 32 | 'selfClass' => self::class, 33 | ]; 34 | 35 | public static array $parentStaticProperty = [ 36 | 'dir' => __DIR__, 37 | 'file' => __FILE__, 38 | 'function' => __FUNCTION__, 39 | 'class' => __CLASS__, 40 | 'trait' => __TRAIT__, 41 | 'method' => __METHOD__, 42 | 'namespace' => __NAMESPACE__, 43 | 'targetClassClass' => TargetClass82::class, 44 | 'targetTraitClass' => TargetTrait82::class, 45 | 'targetParentClass' => TargetParent82::class, 46 | 'selfClass' => self::class, 47 | ]; 48 | 49 | public function parentMethod(): array 50 | { 51 | return [ 52 | 'dir' => __DIR__, 53 | 'file' => __FILE__, 54 | 'function' => __FUNCTION__, 55 | 'class' => __CLASS__, 56 | 'trait' => __TRAIT__, 57 | 'method' => __METHOD__, 58 | 'namespace' => __NAMESPACE__, 59 | 'targetClassClass' => TargetClass82::class, 60 | 'targetTraitClass' => TargetTrait82::class, 61 | 'targetParentClass' => TargetParent82::class, 62 | 'selfClass' => self::class, 63 | 'staticClass' => static::class, 64 | ]; 65 | } 66 | 67 | public static function parentStaticMethod(): array 68 | { 69 | return [ 70 | 'dir' => __DIR__, 71 | 'file' => __FILE__, 72 | 'function' => __FUNCTION__, 73 | 'class' => __CLASS__, 74 | 'trait' => __TRAIT__, 75 | 'method' => __METHOD__, 76 | 'namespace' => __NAMESPACE__, 77 | 'targetClassClass' => TargetClass82::class, 78 | 'targetTraitClass' => TargetTrait82::class, 79 | 'targetParentClass' => TargetParent82::class, 80 | 'selfClass' => self::class, 81 | 'staticClass' => static::class, 82 | ]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/MagicConstants/Target/TargetTrait.php: -------------------------------------------------------------------------------- 1 | __DIR__, 9 | 'file' => __FILE__, 10 | 'function' => __FUNCTION__, 11 | 'class' => __CLASS__, 12 | 'trait' => __TRAIT__, 13 | 'method' => __METHOD__, 14 | 'namespace' => __NAMESPACE__, 15 | 'targetClassClass' => TargetClass::class, 16 | 'targetTraitClass' => TargetTrait::class, 17 | 'targetParentClass' => TargetParent::class, 18 | 'selfClass' => self::class, 19 | ]; 20 | 21 | public static array $traitStaticProperty = [ 22 | 'dir' => __DIR__, 23 | 'file' => __FILE__, 24 | 'function' => __FUNCTION__, 25 | 'class' => __CLASS__, 26 | 'trait' => __TRAIT__, 27 | 'method' => __METHOD__, 28 | 'namespace' => __NAMESPACE__, 29 | 'targetClassClass' => TargetClass::class, 30 | 'targetTraitClass' => TargetTrait::class, 31 | 'targetParentClass' => TargetParent::class, 32 | 'selfClass' => self::class, 33 | ]; 34 | 35 | public function traitMethod(): array 36 | { 37 | return [ 38 | 'dir' => __DIR__, 39 | 'file' => __FILE__, 40 | 'function' => __FUNCTION__, 41 | 'class' => __CLASS__, 42 | 'trait' => __TRAIT__, 43 | 'method' => __METHOD__, 44 | 'namespace' => __NAMESPACE__, 45 | 'targetClassClass' => TargetClass::class, 46 | 'targetTraitClass' => TargetTrait::class, 47 | 'targetParentClass' => TargetParent::class, 48 | 'selfClass' => self::class, 49 | 'staticClass' => static::class, 50 | ]; 51 | } 52 | 53 | public static function traitStaticMethod(): array 54 | { 55 | return [ 56 | 'dir' => __DIR__, 57 | 'file' => __FILE__, 58 | 'function' => __FUNCTION__, 59 | 'class' => __CLASS__, 60 | 'trait' => __TRAIT__, 61 | 'method' => __METHOD__, 62 | 'namespace' => __NAMESPACE__, 63 | 'targetClassClass' => TargetClass::class, 64 | 'targetTraitClass' => TargetTrait::class, 65 | 'targetParentClass' => TargetParent::class, 66 | 'selfClass' => self::class, 67 | 'staticClass' => static::class, 68 | ]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ModifyArgument/Aspect/NumberHelperAspect.php: -------------------------------------------------------------------------------- 1 | getArgument(0); 20 | $numbers = array_filter($numbers, fn($number) => $number >= 0); 21 | $invocation->setArgument(0, $numbers); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ModifyArgument/Kernel.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(NumberHelper::class); 26 | $numberHelper = new NumberHelper(); 27 | 28 | $numbers = [1, 2, 3, 4, 5]; 29 | $expected = 15; 30 | $actual = $numberHelper->sumArray($numbers); 31 | $this->assertEquals($expected, $actual); 32 | 33 | $numbers = [1, 2, -3, 4, 5]; 34 | $expected = 12; 35 | $actual = $numberHelper->sumArray($numbers); 36 | $this->assertEquals($expected, $actual); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ModifyArgument/Target/NumberHelper.php: -------------------------------------------------------------------------------- 1 | getArgument('data'); 21 | $array['metadata'] = 'metadata'; 22 | $invocation->setArgument('data', $array); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ModifyArgumentPassedByReference/Kernel.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(ArrayCreator::class); 22 | $idCreator = new ArrayCreator(); 23 | 24 | $data = 'my-awesome-data'; 25 | $idCreator->createArray($data); 26 | /** @var array $data */ 27 | 28 | $this->assertIsArray($data); 29 | $this->assertArrayHasKey('metadata', $data); 30 | $this->assertEquals('metadata', $data['metadata']); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ModifyArgumentPassedByReference/Target/ArrayCreator.php: -------------------------------------------------------------------------------- 1 | $data]; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/MultipleAdvicesWithSameAdviceTypeOnSameTargetMethod/Aspect/ProfilePictureValidatorAspect.php: -------------------------------------------------------------------------------- 1 | getArgument('image'); 24 | $imageInfo = getimagesize($image); 25 | 26 | $allowedFormats = [IMAGETYPE_PNG]; 27 | 28 | if (!$imageInfo || !in_array($imageInfo[2], $allowedFormats)) { 29 | throw new Exception('Invalid image format'); 30 | } 31 | } 32 | 33 | /** 34 | * @throws Exception 35 | */ 36 | #[Before( 37 | class: ProfileController::class, 38 | method: 'uploadProfilePicture', 39 | )] 40 | public function checkImageSize(BeforeMethodInvocation $invocation): void 41 | { 42 | $image = $invocation->getArgument('image'); 43 | $imageSize = filesize($image); 44 | 45 | // 1 MB 46 | $maxSize = 1048576; 47 | 48 | if ($imageSize > $maxSize) { 49 | throw new Exception('Image is too big'); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/MultipleAdvicesWithSameAdviceTypeOnSameTargetMethod/Kernel.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(ProfileController::class); 32 | $profileController = new ProfileController(); 33 | 34 | // Valid avatar 35 | $path = $profileController->uploadProfilePicture( 36 | 'avatar', 37 | self::AVATAR_PATH, 38 | ); 39 | 40 | // No exception thrown 41 | $this->assertTrue(true); 42 | 43 | $this->assertSame( 44 | 'https://example.com/avatar', 45 | $path, 46 | ); 47 | 48 | // Invalid avatar size 49 | $exceptionThrown = false; 50 | try { 51 | $profileController->uploadProfilePicture( 52 | 'avatar', 53 | self::AVATAR_HQ_PATH, 54 | ); 55 | } catch (Exception $e) { 56 | $exceptionThrown = true; 57 | $this->assertSame( 58 | 'Image is too big', 59 | $e->getMessage(), 60 | ); 61 | } 62 | $this->assertTrue($exceptionThrown); 63 | 64 | // Invalid avatar format 65 | $exceptionThrown = false; 66 | try { 67 | $profileController->uploadProfilePicture( 68 | 'avatar', 69 | self::AVATAR_WRONG_FORMAT_PATH, 70 | ); 71 | } catch (Exception $e) { 72 | $exceptionThrown = true; 73 | $this->assertSame( 74 | 'Invalid image format', 75 | $e->getMessage(), 76 | ); 77 | } 78 | $this->assertTrue($exceptionThrown); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/MultipleAdvicesWithSameAdviceTypeOnSameTargetMethod/Target/ProfileController.php: -------------------------------------------------------------------------------- 1 | addDefinitions([ 31 | GroupPolicy::class => DI\create(GroupPolicy::class), 32 | GroupMemberService::class => DI\autowire(), 33 | ]); 34 | 35 | $container = $containerBuilder->build(); 36 | 37 | $service = $container->get(GroupMemberService::class); 38 | 39 | $this->assertInstanceOf(GroupMemberService::class, $service); 40 | $this->assertEquals( 41 | 'Original Policy Details', 42 | $service->getPolicyDetails(), 43 | ); 44 | } 45 | 46 | public function testManualDefinition(): void 47 | { 48 | Util::clearCache(); 49 | Kernel::init(); 50 | 51 | $containerBuilder = new ContainerBuilder(); 52 | $containerBuilder->addDefinitions([ 53 | GroupPolicy::class => DI\create(GroupPolicy::class), 54 | GroupMemberService::class => static function (ContainerInterface $container) { 55 | return new GroupMemberService( 56 | $container->get(GroupPolicy::class) 57 | ); 58 | } 59 | ]); 60 | 61 | $container = $containerBuilder->build(); 62 | 63 | $service = $container->get(GroupMemberService::class); 64 | 65 | $this->assertInstanceOf(GroupMemberService::class, $service); 66 | $this->assertEquals( 67 | 'Original Policy Details', 68 | $service->getPolicyDetails(), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/NewClassCreationWithProxiedClasses/Target/GroupMemberService.php: -------------------------------------------------------------------------------- 1 | groupPolicy = $groupPolicy; 12 | } 13 | 14 | public function getPolicyDetails(): string 15 | { 16 | return $this->groupPolicy->getPolicyDetails(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/NewClassCreationWithProxiedClasses/Target/GroupPolicy.php: -------------------------------------------------------------------------------- 1 | addTrace('DefaultAspect '.$invocation->getMethodName()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/OnlyPublicMethods/Aspect/OnlyPublicMethodsAspect.php: -------------------------------------------------------------------------------- 1 | addTrace('OnlyPublicMethodsAspect '.$invocation->getMethodName()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/OnlyPublicMethods/Kernel.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(TargetClass::class); 29 | $targetClass = new TargetClass(); 30 | 31 | $targetClass->helloWorld(); 32 | $targetClass->parentHelloWorld(); 33 | $targetClass->askParentHelloHere(); 34 | $targetClass->traitHelloWorld(); 35 | $targetClass->askTraitHelloHere(); 36 | 37 | $stackTrace = StackTrace::getInstance(); 38 | $this->assertEquals( 39 | [ 40 | // Call to $targetClass->helloWorld() = 2 Advice invocations 41 | 'DefaultAspect helloWorld', 42 | 'OnlyPublicMethodsAspect helloWorld', 43 | // Call to $targetClass->parentHelloWorld() = 2 Advice invocations 44 | 'DefaultAspect parentHelloWorld', 45 | 'OnlyPublicMethodsAspect parentHelloWorld', 46 | // Call to $targetClass->askParentHelloHere() = 3 Advice invocations 47 | 'DefaultAspect parentHelloHere', 48 | 'DefaultAspect askParentHelloHere', 49 | 'OnlyPublicMethodsAspect askParentHelloHere', 50 | // Call to $targetClass->traitHelloWorld() = 2 Advice invocations 51 | 'DefaultAspect traitHelloWorld', 52 | 'OnlyPublicMethodsAspect traitHelloWorld', 53 | // Call to $targetClass->askTraitHelloHere() = 3 Advice invocations 54 | 'DefaultAspect traitHelloHere', 55 | 'DefaultAspect askTraitHelloHere', 56 | 'OnlyPublicMethodsAspect askTraitHelloHere', 57 | ], 58 | $stackTrace->getStackTrace(), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/OnlyPublicMethods/Target/TargetClass.php: -------------------------------------------------------------------------------- 1 | traitHelloHere(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/OnlyPublicMethods/Target/TargetParentClass.php: -------------------------------------------------------------------------------- 1 | proceed(); 20 | $result = $result / (1 - BankingSystem::DEPOSIT_FEE_PERCENTAGE / 100); 21 | 22 | $invocation->setResult($result); 23 | } 24 | 25 | #[After( 26 | BankingSystem::class, 27 | 'addFeeToWithdraw', 28 | )] 29 | public function removeFeeFromWithdraw(AfterMethodInvocation $invocation): void 30 | { 31 | $result = $invocation->proceed(); 32 | $result = $result / (1 + BankingSystem::WITHDRAW_FEE_PERCENTAGE / 100); 33 | 34 | $invocation->setResult($result); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ProtectedAndPrivateMethods/Kernel.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(BankingSystem::class); 27 | $bankingSystem = new BankingSystem(); 28 | 29 | $bankingSystem->deposit(100.0); 30 | $balance = $bankingSystem->getBalance(); 31 | 32 | $this->assertEquals( 33 | 100.0, 34 | $balance, 35 | ); 36 | 37 | $bankingSystem->withdraw(50.0); 38 | $balance = $bankingSystem->getBalance(); 39 | 40 | $this->assertEquals( 41 | 50.0, 42 | $balance, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/ProtectedAndPrivateMethods/Target/BankingSystem.php: -------------------------------------------------------------------------------- 1 | balance += $this->removeFeeFromDeposit($amount); 15 | } 16 | 17 | protected function removeFeeFromDeposit(float $amount): float 18 | { 19 | return $amount - ($amount * self::DEPOSIT_FEE_PERCENTAGE / 100); 20 | } 21 | 22 | public function withdraw(float $amount): void 23 | { 24 | $this->balance -= $this->addFeeToWithdraw($amount); 25 | } 26 | 27 | private function addFeeToWithdraw(float $amount): float 28 | { 29 | return $amount + ($amount * self::WITHDRAW_FEE_PERCENTAGE / 100); 30 | } 31 | 32 | public function getBalance(): float 33 | { 34 | return $this->balance; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/Readonly/Aspect/ReadonlyAspect.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Readonly classes are supported only in PHP 8.2 and later.'); 25 | } 26 | 27 | Util::clearCache(); 28 | Kernel::init(); 29 | 30 | $this->assertWillBeWoven(ReadonlyClass::class); 31 | 32 | new ReadonlyClass(); 33 | 34 | $this->assertTrue(true); 35 | } 36 | 37 | public function testReadonlyPromotedProperties(): void 38 | { 39 | Util::clearCache(); 40 | Kernel::init(); 41 | 42 | $this->assertWillBeWoven(ReadonlyPromotedProperties::class); 43 | 44 | new ReadonlyPromotedProperties('Walter Woshid', 42); 45 | 46 | $this->assertTrue(true); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/Readonly/Target/ReadonlyClass.php: -------------------------------------------------------------------------------- 1 | getArguments(); 22 | 23 | $cacheKey = md5(serialize($arguments)); 24 | 25 | $cachedRoutes = $this->getFromCache($cacheKey); 26 | if ($cachedRoutes) { 27 | $invocation->setResult($cachedRoutes); 28 | return; 29 | } 30 | 31 | $routes = $invocation->proceed(); 32 | 33 | $this->storeInCache($cacheKey, $routes); 34 | } 35 | 36 | private function getFromCache(string $cacheKey): ?array 37 | { 38 | return self::$cachedRoutes[$cacheKey] ?? null; 39 | } 40 | 41 | private function storeInCache(string $cacheKey, array $routes): void 42 | { 43 | self::$cachedRoutes[$cacheKey] = $routes; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/TraitAdvice/Kernel.php: -------------------------------------------------------------------------------- 1 | ['/users', 'UserController@index'], 11 | ]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/TraitAdvice/Target/Router.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(Router::class); 26 | $router = new Router(); 27 | 28 | $routes = $router->getRoutes(); 29 | $this->assertCount(1, $routes); 30 | 31 | $cachedRoutes = RouteCachingAspect::$cachedRoutes; 32 | $this->assertCount(1, $cachedRoutes); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/VariadicParameters/Aspect/StringPrefixerAspect.php: -------------------------------------------------------------------------------- 1 | getArgument('prefix'); 22 | $ids = $invocation->getArgument('ids'); 23 | 24 | foreach ($ids as &$id) { 25 | $id = $prefix . '-' . $id; 26 | } 27 | 28 | $invocation->setArgument('ids', $ids); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Functional/AdviceBehavior/VariadicParameters/Target/IdHelper.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(IdHelper::class); 29 | $idHelper = new IdHelper(); 30 | 31 | $result = $idHelper->createIds('prefix', ...$ids); 32 | 33 | $expectedResult = ['prefix-id1', 'prefix-id2', 'prefix-id3']; 34 | 35 | $this->assertSame($expectedResult, $result); 36 | } 37 | 38 | public function testVariadicParametersWithoutAop(): void 39 | { 40 | Util::clearCache(); 41 | 42 | $ids = ['id1', 'id2', 'id3']; 43 | 44 | $idHelper = new IdHelper(); 45 | 46 | $result = $idHelper->createIds('prefix', ...$ids); 47 | 48 | $expectedResult = ['id1', 'id2', 'id3']; 49 | 50 | $this->assertSame($expectedResult, $result); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/AdviceMatchingMultipleClassesAndMethods/AdviceMatchingMultipleClassesAndMethodsTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(Product::class); 27 | $product = new Product(); 28 | $productPrice = $product->getPrice(); 29 | $this->assertEquals(90.00, $productPrice); 30 | 31 | $this->assertWillBeWoven(Order::class); 32 | $order = new Order(); 33 | $orderTotal = $order->getTotal(); 34 | $this->assertEquals(400.00, $orderTotal); 35 | } 36 | 37 | public function testCachedAdviceMatchingMultipleClassesAndMethods(): void 38 | { 39 | Kernel::init(); 40 | 41 | $this->assertAspectLoadedFromCache(Product::class); 42 | $product = new Product(); 43 | $productPrice = $product->getPrice(); 44 | $this->assertEquals(90.00, $productPrice); 45 | 46 | $this->assertAspectLoadedFromCache(Order::class); 47 | $order = new Order(); 48 | $orderTotal = $order->getTotal(); 49 | $this->assertEquals(400.00, $orderTotal); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/AdviceMatchingMultipleClassesAndMethods/Aspect/DiscountAspect.php: -------------------------------------------------------------------------------- 1 | getSubject(); 21 | 22 | $productDiscount = 0.1; 23 | $orderDiscount = 0.2; 24 | 25 | if ($subject instanceof Product) { 26 | $oldPrice = $invocation->proceed(); 27 | $newPrice = $oldPrice - ($oldPrice * $productDiscount); 28 | 29 | $invocation->setResult($newPrice); 30 | } 31 | 32 | if ($subject instanceof Order) { 33 | $oldTotal = $invocation->proceed(); 34 | $newTotal = $oldTotal - ($oldTotal * $orderDiscount); 35 | 36 | $invocation->setResult($newTotal); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/AdviceMatchingMultipleClassesAndMethods/Kernel.php: -------------------------------------------------------------------------------- 1 | total; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/AdviceMatchingMultipleClassesAndMethods/Target/Product.php: -------------------------------------------------------------------------------- 1 | price; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/ClassHierarchyOnlyInvokedOnce/Aspect.php: -------------------------------------------------------------------------------- 1 | addTrace("Method call $count"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/ClassHierarchyOnlyInvokedOnce/ClassHierarchyOnlyInvokedOnceTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(TargetClassC::class); 27 | $instance = new TargetClassA(); 28 | $instance->helloWorld(); 29 | $instance->helloWorld(); 30 | 31 | $stackTrace = StackTrace::getInstance(); 32 | 33 | $this->assertEquals( 34 | [ 35 | 'Method call 1', 36 | 'Method call 2', 37 | ], 38 | $stackTrace->getStackTrace(), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/ClassHierarchyOnlyInvokedOnce/Kernel.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(TargetClass::class); 30 | $this->assertAspectNotApplied(TargetTrait::class); 31 | $targetClass = new TargetClass(); 32 | 33 | $targetClass->helloWorld(); 34 | $targetClass->helloHere(); 35 | 36 | $stackTrace = StackTrace::getInstance(); 37 | $this->assertEquals( 38 | [ 39 | // First call to TargetClass::helloWorld() 40 | 'DefaultAspect', 41 | 'InterceptTraitMethodsAspect', 42 | // Second call to TargetTrait::helloHere() 43 | 'DefaultAspect', 44 | ], 45 | $stackTrace->getStackTrace(), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/InterceptTraitMethods/Aspect/DefaultAspect.php: -------------------------------------------------------------------------------- 1 | addTrace('DefaultAspect'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/InterceptTraitMethods/Aspect/InterceptTraitMethodsAspect.php: -------------------------------------------------------------------------------- 1 | addTrace('InterceptTraitMethodsAspect'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/InterceptTraitMethods/Kernel.php: -------------------------------------------------------------------------------- 1 | assertAspectNotApplied(Car::class); 23 | $car = new Car(); 24 | /** @noinspection PhpDeprecationInspection */ 25 | $car->startCar(); 26 | 27 | $this->expectOutputString('Car started'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/JetBrainsAttribute/Target/Car.php: -------------------------------------------------------------------------------- 1 | getArgument('salaryIncrease'); 20 | 21 | $invocation->setArgument( 22 | 'salaryIncrease', 23 | $salary * 2, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/SelfType/Kernel.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(Employee::class); 28 | $this->assertWillBeWoven(AbstractEmployee::class); 29 | $employee = new Employee('Walter', 3000.0); 30 | 31 | $salaryIncrease = 1000.0; 32 | 33 | $promotedEmployee = $employee->promote($employee, $salaryIncrease); 34 | 35 | $this->assertInstanceOf(Employee::class, $promotedEmployee); 36 | $this->assertInstanceOf(AbstractEmployee::class, $promotedEmployee); 37 | $this->assertSame( 38 | $employee->getName(), 39 | $promotedEmployee->getName(), 40 | ); 41 | $this->assertSame( 42 | $employee->getSalary() + ($salaryIncrease * 2), 43 | $promotedEmployee->getSalary(), 44 | ); 45 | 46 | 47 | $salaryDecrease = 1000.0; 48 | 49 | $demotedEmployee = $promotedEmployee->demote($promotedEmployee, $salaryDecrease); 50 | 51 | $this->assertInstanceOf(PartTimeEmployee::class, $demotedEmployee); 52 | $this->assertInstanceOf(Employee::class, $demotedEmployee); 53 | $this->assertInstanceOf(AbstractEmployee::class, $demotedEmployee); 54 | $this->assertSame( 55 | $promotedEmployee->getName(), 56 | $demotedEmployee->getName(), 57 | ); 58 | $this->assertSame( 59 | $promotedEmployee->getSalary() - $salaryDecrease, 60 | $demotedEmployee->getSalary(), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/SelfType/Target/AbstractEmployee.php: -------------------------------------------------------------------------------- 1 | name; 17 | } 18 | 19 | public function getSalary(): float 20 | { 21 | return $this->salary; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/SelfType/Target/Employee.php: -------------------------------------------------------------------------------- 1 | getSalary() + $salaryIncrease; 15 | 16 | return new self($employee->getName(), $promotedSalary); 17 | } 18 | 19 | public function demote(Employee $employee, float $salaryDecrease): PartTimeEmployee 20 | { 21 | $demotedSalary = $employee->getSalary() - $salaryDecrease; 22 | 23 | return new PartTimeEmployee($employee->getName(), $demotedSalary); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Functional/AspectMatching/SelfType/Target/PartTimeEmployee.php: -------------------------------------------------------------------------------- 1 | expectException(AspectNotFoundException::class); 23 | 24 | InvalidAspectClassNameKernel::init(); 25 | } 26 | 27 | /** 28 | * @see InvalidAspectsTypeKernel 29 | */ 30 | public function testInvalidAspectType(): void 31 | { 32 | $this->expectException(InvalidAspectClassNameException::class); 33 | 34 | InvalidAspectsTypeKernel::init(); 35 | } 36 | 37 | /** 38 | * @see InvalidAspectClassKernel 39 | */ 40 | public function testInvalidAspectClass(): void 41 | { 42 | $this->expectException(MissingAspectAttributeException::class); 43 | 44 | InvalidAspectClassKernel::init(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Functional/ErrorHandling/InvalidAspect/Kernel/InvalidAspectClassKernel.php: -------------------------------------------------------------------------------- 1 | getArgument('itemName'); 19 | $quantity = $invocation->getArgument('quantity'); 20 | 21 | $logMessage = sprintf( 22 | "Item %s added to inventory with quantity %d.", 23 | $itemName, 24 | $quantity, 25 | ); 26 | 27 | $logger = Logger::getInstance(); 28 | $logger->log($logMessage); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Functional/ErrorHandling/MissingClassOrMethod/Aspect/GetQuantityLoggerAspect.php: -------------------------------------------------------------------------------- 1 | getArgument('itemName'); 17 | $quantity = $invocation->proceed(); 18 | 19 | $logMessage = sprintf( 20 | "Item %s has quantity %d.", 21 | $itemName, 22 | $quantity, 23 | ); 24 | 25 | $logger = Logger::getInstance(); 26 | $logger->log($logMessage); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Functional/ErrorHandling/MissingClassOrMethod/Aspect/RemoveItemLoggerAspect.php: -------------------------------------------------------------------------------- 1 | getArgument('itemName'); 20 | 21 | $logMessage = sprintf( 22 | "Item %s removed from inventory.", 23 | $itemName, 24 | ); 25 | 26 | $logger = Logger::getInstance(); 27 | $logger->log($logMessage); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Functional/ErrorHandling/MissingClassOrMethod/Kernel/AddItemKernel.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 39 | MissingClassNameException::class, 40 | $error, 41 | ); 42 | } 43 | 44 | /** 45 | * @see RemoveItemLoggerAspect::logRemoveItem() 46 | */ 47 | public function testMissingMethodName(): void 48 | { 49 | Util::clearCache(); 50 | 51 | $error = null; 52 | 53 | try { 54 | RemoveItemKernel::init(); 55 | new InventoryManager(); 56 | } catch (Exception $e) { 57 | $error = $e; 58 | } 59 | 60 | $this->assertInstanceOf( 61 | MissingMethodNameException::class, 62 | $error, 63 | ); 64 | } 65 | 66 | /** 67 | * @see GetQuantityLoggerAspect::logGetQuantity() 68 | */ 69 | public function testMissingClassAndMethodName(): void 70 | { 71 | Util::clearCache(); 72 | 73 | $error = null; 74 | 75 | try { 76 | GetQuantityKernel::init(); 77 | new InventoryManager(); 78 | } catch (Exception $e) { 79 | $error = $e; 80 | } 81 | 82 | $missingClassNameException = $error instanceof MissingClassNameException; 83 | $missingMethodNameException = $error instanceof MissingMethodNameException; 84 | 85 | $this->assertTrue( 86 | $missingClassNameException || $missingMethodNameException, 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/Functional/ErrorHandling/MissingClassOrMethod/Target/InventoryManager.php: -------------------------------------------------------------------------------- 1 | items[$itemName] = $quantity; 12 | } 13 | 14 | public function removeItem(string $itemName): void 15 | { 16 | unset($this->items[$itemName]); 17 | } 18 | 19 | public function getQuantity(string $itemName): int 20 | { 21 | return $this->items[$itemName] ?? 0; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Functional/Kernel/CustomDependencyInjectionHandler/Aspect.php: -------------------------------------------------------------------------------- 1 | assertStringContainsString( 23 | 'Generating aspect/transformer instance: ' . Aspect::class, 24 | $output, 25 | ); 26 | 27 | $class = new Target(); 28 | 29 | $this->assertSame( 30 | 420, 31 | $class->answer(), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Functional/Kernel/CustomDependencyInjectionHandler/Kernel.php: -------------------------------------------------------------------------------- 1 | proceed(); 20 | $invocation->setResult(!$result); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Integration/TransformerAndAspect/Kernel.php: -------------------------------------------------------------------------------- 1 | getSourceFileNode(); 20 | 21 | foreach ($sourceFileNode->getDescendantNodes() as $node) { 22 | if ($node instanceof QualifiedName && $node->getText() === 'is_real') { 23 | $code->edit( 24 | $node->nameParts[0], 25 | 'is_float', 26 | ); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Integration/TransformerAndAspect/TransformerAndAspectTest.php: -------------------------------------------------------------------------------- 1 | assertWillBeWoven(DeprecatedAndWrongClass::class); 28 | $class = new DeprecatedAndWrongClass(); 29 | $this->assertTrue($class->checkIfFloat(1.0)); 30 | } 31 | 32 | public function testCachedTransformerAndAspect(): void 33 | { 34 | Kernel::init(); 35 | 36 | $this->assertAspectLoadedFromCache(DeprecatedAndWrongClass::class); 37 | $class = new DeprecatedAndWrongClass(); 38 | $this->assertTrue($class->checkIfFloat(42.0)); 39 | $this->assertFalse($class->checkIfFloat("Hello World!")); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Integration/TransformerAndAspectDependencyInjectionHandler/Aspect.php: -------------------------------------------------------------------------------- 1 | proceed(); 19 | 20 | return $result + 378; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Integration/TransformerAndAspectDependencyInjectionHandler/Kernel.php: -------------------------------------------------------------------------------- 1 | getSourceFileNode(); 22 | 23 | foreach ($sourceFileNode->getDescendantNodes() as $node) { 24 | if ($node instanceof QualifiedNameList 25 | && $node->getFirstAncestor(MethodDeclaration::class)?->getName() === 'answer' 26 | ) { 27 | $code->edit($node, 'int|float'); 28 | } 29 | 30 | if ($node instanceof NumericLiteral 31 | && $node->getFirstAncestor(MethodDeclaration::class)?->getName() === 'answer' 32 | ) { 33 | $text = $node->getText(); 34 | $code->edit($node, "$text.69"); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Integration/TransformerAndAspectDependencyInjectionHandler/TransformerAndAspectDependencyInjectionHandlerTest.php: -------------------------------------------------------------------------------- 1 | assertStringContainsString( 30 | 'Generating aspect instance: ' . Aspect::class, 31 | $output, 32 | ); 33 | $this->assertStringContainsString( 34 | 'Generating transformer instance: ' . Transformer::class, 35 | $output, 36 | ); 37 | 38 | $this->assertWillBeWoven(Target::class); 39 | $class = new Target(); 40 | 41 | $this->assertSame( 42 | 420.69, 43 | $class->answer(), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Performance/.gitignore: -------------------------------------------------------------------------------- 1 | Temp 2 | -------------------------------------------------------------------------------- /tests/Performance/Aspect/AddOneAspect.php: -------------------------------------------------------------------------------- 1 | proceed(); 20 | 21 | $invocation->setResult($result + 1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Performance/Kernel/MeasurePerformanceKernel.php: -------------------------------------------------------------------------------- 1 | add($number); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Performance/Target/Numbers.php: -------------------------------------------------------------------------------- 1 | number; 12 | } 13 | 14 | public function add(int $number): void 15 | { 16 | $this->number += $number; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Stubs/Etc/Logger.php: -------------------------------------------------------------------------------- 1 | log[] = $message; 16 | } 17 | 18 | public function getLogs(): array 19 | { 20 | return $this->log; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Stubs/Etc/MailQueue.php: -------------------------------------------------------------------------------- 1 | mails[] = $mail; 16 | } 17 | 18 | public function getMails(): array 19 | { 20 | return $this->mails; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Stubs/Etc/StackTrace.php: -------------------------------------------------------------------------------- 1 | stackTrace[] = $trace; 16 | } 17 | 18 | public function getStackTrace(): array 19 | { 20 | return $this->stackTrace; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Stubs/Kernel/EmptyKernel.php: -------------------------------------------------------------------------------- 1 |