├── .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 |