diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 3c6dd2e9..75495816 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -13,7 +13,7 @@ jobs: - name: "Install PHP" uses: shivammathur/setup-php@v2 with: - php-version: "8.2" + php-version: "8.4" ini-values: memory_limit=-1 tools: composer:v2 - name: "Cache dependencies" @@ -22,9 +22,9 @@ jobs: path: | ~/.composer/cache vendor - key: "php-8.2" - restore-keys: "php-8.2" + key: "php-8.4" + restore-keys: "php-8.4" - name: "Install dependencies" - run: "composer install --no-interaction --no-progress --no-suggest" + run: "composer install --no-interaction --no-progress" - name: "Static analysis" - uses: chindit/actions-phpstan@master + run: "vendor/bin/phpstan analyze --memory-limit=512M" diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index e06135ba..ac9a9853 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -16,8 +16,8 @@ jobs: - "lowest" - "highest" php-version: - - "8.2" - - "8.3" + - "8.4" + - "8.5" operating-system: - "ubuntu-latest" @@ -44,15 +44,15 @@ jobs: - name: "Install lowest dependencies" if: ${{ matrix.dependencies == 'lowest' }} - run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest" + run: "composer update --prefer-lowest --no-interaction --no-progress" - name: "Install highest dependencies" if: ${{ matrix.dependencies == 'highest' }} - run: "composer update --no-interaction --no-progress --no-suggest" + run: "composer update --no-interaction --no-progress" - name: "Install locked dependencies" if: ${{ matrix.dependencies == 'locked' }} - run: "composer install --no-interaction --no-progress --no-suggest" + run: "composer install --no-interaction --no-progress" - name: "Tests" run: "vendor/bin/phpunit" diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 00000000..681311eb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..b932b8dd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,88 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**Go! AOP Framework** — an Aspect-Oriented Programming (AOP) framework for PHP 8.4+. It intercepts PHP class/method/function execution transparently by transforming source code at load time via a custom PHP stream wrapper, without requiring PECL extensions, annotations at runtime, or eval. + +Package: `goaop/framework` | Namespace root: `Go\` | PHP: `^8.4.0` + +## Commands + +```bash +# Install dependencies +composer install + +# Run full test suite +./vendor/bin/phpunit + +# Run a single test file +./vendor/bin/phpunit tests/Go/Core/ContainerTest.php + +# Run a single test method +./vendor/bin/phpunit --filter testMethodName tests/Go/Core/ContainerTest.php + +# Static analysis (level 4, src/ only) +./vendor/bin/phpstan analyze + +# CLI debugging tools +./bin/aspect debug:advisors [class] +./bin/aspect debug:pointcuts [expression] +``` + +## Architecture + +The framework works by intercepting PHP's class loading pipeline. When a class is loaded, the stream wrapper transforms its source code to inject interception hooks, then stores the result in a cache directory. The transformed class contains calls into the advisor chain for each matched join point. + +### Initialization flow + +1. **`AspectKernel::init()`** (`src/Core/AspectKernel.php`) — singleton, registers stream wrapper, builds transformer chain, calls `configureAop()` where users register aspects +2. **`SourceTransformingLoader::register()`** (`src/Instrument/ClassLoading/SourceTransformingLoader.php`) — PHP stream wrapper that intercepts `include`/`require` via the `go-aop-php://` protocol +3. **`AopComposerLoader::init()`** (`src/Instrument/ClassLoading/AopComposerLoader.php`) — hooks into Composer's autoloader to redirect loads through the stream wrapper +4. **`CachingTransformer`** — outer transformer that manages cache; on cache miss, invokes the inner transformers and writes the result + +### Transformer chain (inner, registered in `AspectKernel::registerTransformers()`) + +Applied in order for each loaded file: +- `ConstructorExecutionTransformer` — transforms `new` expressions (when `INTERCEPT_INITIALIZATIONS` feature enabled) +- `FilterInjectorTransformer` — wraps `include`/`require` (when `INTERCEPT_INCLUDES` enabled) +- `SelfValueTransformer` — rewrites `self::` to use the concrete proxy class +- `WeavingTransformer` — main transformer; uses `AdviceMatcher` to find applicable advices and `CachedAspectLoader` for aspect metadata, then delegates to proxy generators +- `MagicConstantTransformer` — rewrites `__FILE__`/`__DIR__` so they resolve to the original file, not the cached proxy + +Each transformer returns `TransformerResultEnum`: `RESULT_TRANSFORMED`, `RESULT_ABSTAIN`, or `RESULT_ABORTED`. + +### Proxy generation (`src/Proxy/`) + +- `ClassProxyGenerator` — generates a proxy subclass with overridden interceptable methods +- `FunctionProxyGenerator` — generates function wrappers +- `TraitProxyGenerator` — generates trait proxies +- `src/Proxy/Part/` — individual code-generation components (method lists, parameter lists, joinpoint property injection) + +### AOP core (`src/Aop/`) + +- `src/Aop/Intercept/` — interfaces: `Joinpoint`, `Invocation`, `MethodInvocation`, `ConstructorInvocation`, `FunctionInvocation`, `FieldAccess` +- `src/Aop/Framework/` — concrete invocation implementations used at runtime by proxies; `AbstractMethodInvocation`, `DynamicClosureMethodInvocation`, `StaticClosureMethodInvocation`, `ClassFieldAccess`, etc. +- `src/Aop/Pointcut/` — LALR pointcut grammar (`PointcutGrammar`, `PointcutParser`, `PointcutLexer`, `PointcutParseTable`) and pointcut combinators (`AndPointcut`, `OrPointcut`, `NotPointcut`, `NamePointcut`, `AttributePointcut`, etc.) +- `src/Lang/Attribute/` — PHP 8 attributes for declaring aspects and advice: `#[Aspect]`, `#[Before]`, `#[After]`, `#[Around]`, `#[AfterThrowing]`, `#[Pointcut]`, `#[DeclareError]`, `#[DeclareParents]` +- `src/Aop/Features.php` — bitmask enum for optional features (`INTERCEPT_FUNCTIONS`, `INTERCEPT_INITIALIZATIONS`, `INTERCEPT_INCLUDES`) + +### Container and aspect loading (`src/Core/`) + +- `Container.php` — DI container with `add()` (by class-string or key), `getService()`, `addLazyService()` (Closure), and automatic tagging by interface +- `AspectLoader` / `CachedAspectLoader` — scan aspect classes for pointcut/advice attributes and produce `Advisor` instances +- `AttributeAspectLoaderExtension` — handles PHP 8 attribute-based aspect definitions +- `AdviceMatcher` — given a class reflector, returns the set of applicable advisors keyed by join point + +### Bridge + +`src/Bridge/Doctrine/MetadataLoadInterceptor.php` — workaround for Doctrine ORM entity weaving (Doctrine loads metadata before the kernel can intercept classes). + +## Test conventions + +- Tests mirror the `src/` structure under `tests/Go/` +- Functional/integration tests live in `tests/Go/Functional/` +- Test fixtures (stub classes for weaving) live in `tests/Go/Stubs/` and `tests/Fixtures/project/src/` (autoloaded as `Go\Tests\TestProject\`) +- PHPUnit 11, bootstrap is `vendor/autoload.php` (no separate test bootstrap) +- PHPStan baseline is `phpstan-baseline.php` — add new accepted errors there rather than inline suppression when appropriate \ No newline at end of file diff --git a/README.md b/README.md index 33ff7154..afb6e49e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Go! AOP is a modern aspect-oriented framework in plain PHP with rich features fo [![Total Downloads](https://img.shields.io/packagist/dt/goaop/framework.svg)](https://packagist.org/packages/goaop/framework) [![Daily Downloads](https://img.shields.io/packagist/dd/goaop/framework.svg)](https://packagist.org/packages/goaop/framework) [![SensioLabs Insight](https://img.shields.io/sensiolabs/i/34549463-37d3-4368-94f5-812880d3ce4c.svg)](https://insight.sensiolabs.com/projects/34549463-37d3-4368-94f5-812880d3ce4c) -[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%208.2-8892BF.svg)](https://php.net/) +[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%208.4-8892BF.svg)](https://www.php.net/supported-versions.php) [![License](https://img.shields.io/packagist/l/goaop/framework.svg)](https://packagist.org/packages/goaop/framework) Features @@ -66,7 +66,7 @@ After that just configure your web server to `demos/` folder and open it in your Ask composer to download the latest version of Go! AOP framework with its dependencies by running the command: -``` bash +```bash composer require goaop/framework ``` @@ -83,7 +83,7 @@ application in one place. The framework provides base class to make it easier to create your own kernel. To create your application kernel, extend the abstract class `Go\Core\AspectKernel` -``` php +```php init([ Aspect is the key element of AOP philosophy. Go! AOP framework just uses simple PHP classes for declaring aspects, which makes it possible to use all features of OOP for aspect classes. As an example let's intercept all the methods and display their names: -``` php +```php // Aspect/MonitorAspect.php namespace Aspect; @@ -144,10 +140,10 @@ namespace Aspect; use Go\Aop\Aspect; use Go\Aop\Intercept\FieldAccess; use Go\Aop\Intercept\MethodInvocation; -use Go\Lang\Annotation\After; -use Go\Lang\Annotation\Before; -use Go\Lang\Annotation\Around; -use Go\Lang\Annotation\Pointcut; +use Go\Lang\Attribute\After; +use Go\Lang\Attribute\Before; +use Go\Lang\Attribute\Around; +use Go\Lang\Attribute\Pointcut; /** * Monitor aspect @@ -157,10 +153,8 @@ class MonitorAspect implements Aspect /** * Method that will be called before real method - * - * @param MethodInvocation $invocation Invocation - * @Before("execution(public Example->*(*))") */ + #[Before("execution(public Example->*(*))")] public function beforeMethodExecution(MethodInvocation $invocation) { echo 'Calling Before Interceptor for: ', @@ -173,8 +167,8 @@ class MonitorAspect implements Aspect ``` Easy, isn't it? We declared here that we want to install a hook before the execution of -all dynamic public methods in the class Example. This is done with the help of annotation -`@Before("execution(public Example->*(*))")` +all dynamic public methods in the class Example. This is done with the help of attribute +`#[Before("execution(public Example->*(*))")]` Hooks can be of any types, you will see them later. But we don't change any code in the class Example! I can feel your astonishment now. diff --git a/composer.json b/composer.json index f30c0ab8..a9db279e 100644 --- a/composer.json +++ b/composer.json @@ -8,19 +8,19 @@ "license": "MIT", "require": { - "php": "^8.2.0", + "php": "^8.4.0", "ext-tokenizer": "*", "goaop/parser-reflection": "4.x-dev", "goaop/dissect": "^3.0", - "laminas/laminas-code": "^4.13", - "symfony/finder": "^5.4 || ^6.4 || ^7.0" + "laminas/laminas-code": "^4.17", + "symfony/finder": "^6.4 || ^7.0" }, "require-dev": { "adlawson/vfs": "^0.12.1", "doctrine/orm": "^2.5 || ^3.0", - "phpstan/phpstan": "^1.10.57", - "phpunit/phpunit": "^10.5.10", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.0", "symfony/console": "^6.4 || ^7.0", "symfony/filesystem": "^6.4 || ^7.0", "symfony/process": "^6.4 || ^7.0", diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 1038f02d..bded0a2d 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -2,14 +2,16 @@ $ignoreErrors = []; $ignoreErrors[] = [ - 'message' => '#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\\\:\\:\\$table \\(array\\{name\\: string, schema\\?\\: string, indexes\\?\\: array, uniqueConstraints\\?\\: array, options\\?\\: array\\, quoted\\?\\: bool\\}\\) does not accept array\\{\\}\\.$#', + 'message' => '#^Property Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata\\\\:\\:\\$table \\(array\\{name\\: string, schema\\?\\: string, indexes\\?\\: array, uniqueConstraints\\?\\: array, options\\?\\: array\\, quoted\\?\\: bool\\}\\) does not accept array\\{\\}\\.$#', + 'identifier' => 'assign.propertyType', 'count' => 1, 'path' => __DIR__ . '/src/Bridge/Doctrine/MetadataLoadInterceptor.php', ]; $ignoreErrors[] = [ - 'message' => '#^Call to function file_get_contents\\(\\) on a separate line has no effect\\.$#', + 'message' => '#^Trait Go\\\\Proxy\\\\Part\\\\PropertyInterceptionTrait is used zero times and is not analysed\\.$#', + 'identifier' => 'trait.unused', 'count' => 1, - 'path' => __DIR__ . '/src/Instrument/ClassLoading/CacheWarmer.php', + 'path' => __DIR__ . '/src/Proxy/Part/PropertyInterceptionTrait.php', ]; return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; diff --git a/src/Aop/Pointcut.php b/src/Aop/Pointcut.php index 2887855f..f787ad58 100644 --- a/src/Aop/Pointcut.php +++ b/src/Aop/Pointcut.php @@ -82,7 +82,7 @@ public function getKind(): int; public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool; } \ No newline at end of file diff --git a/src/Aop/Pointcut/AndPointcut.php b/src/Aop/Pointcut/AndPointcut.php index 4d998748..3d53a353 100644 --- a/src/Aop/Pointcut/AndPointcut.php +++ b/src/Aop/Pointcut/AndPointcut.php @@ -39,7 +39,7 @@ /** * And constructor */ - public function __construct(int $pointcutKind = null, Pointcut ...$pointcuts) + public function __construct(?int $pointcutKind = null, Pointcut ...$pointcuts) { // If we don't have specified kind, it will be calculated as intersection then if (!isset($pointcutKind)) { @@ -55,8 +55,8 @@ public function __construct(int $pointcutKind = null, Pointcut ...$pointcuts) public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { foreach ($this->pointcuts as $singlePointcut) { if (!$singlePointcut->matches($context, $reflector, $instanceOrScope, $arguments)) { diff --git a/src/Aop/Pointcut/AttributePointcut.php b/src/Aop/Pointcut/AttributePointcut.php index 2e6aaa3f..5264c28f 100644 --- a/src/Aop/Pointcut/AttributePointcut.php +++ b/src/Aop/Pointcut/AttributePointcut.php @@ -44,8 +44,8 @@ public function __construct( final public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { // If we don't use context for matching and we do static check, then always match if (!$this->useContextForMatching && !isset($reflector)) { diff --git a/src/Aop/Pointcut/ClassInheritancePointcut.php b/src/Aop/Pointcut/ClassInheritancePointcut.php index b6f29997..ba2af852 100644 --- a/src/Aop/Pointcut/ClassInheritancePointcut.php +++ b/src/Aop/Pointcut/ClassInheritancePointcut.php @@ -34,8 +34,8 @@ public function __construct(private string $parentClassOrInterfaceName) {} public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { // We match only with ReflectionClass as a context if (!$context instanceof ReflectionClass) { diff --git a/src/Aop/Pointcut/MagicMethodDynamicPointcut.php b/src/Aop/Pointcut/MagicMethodDynamicPointcut.php index 3c03f30f..df23f696 100644 --- a/src/Aop/Pointcut/MagicMethodDynamicPointcut.php +++ b/src/Aop/Pointcut/MagicMethodDynamicPointcut.php @@ -51,8 +51,8 @@ public function __construct(private string $methodName) { public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { // Magic methods can be only inside class context if (!$context instanceof ReflectionClass) { diff --git a/src/Aop/Pointcut/MatchInheritedPointcut.php b/src/Aop/Pointcut/MatchInheritedPointcut.php index 23bcd3e2..8adeb1aa 100644 --- a/src/Aop/Pointcut/MatchInheritedPointcut.php +++ b/src/Aop/Pointcut/MatchInheritedPointcut.php @@ -29,8 +29,8 @@ final class MatchInheritedPointcut implements Pointcut public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { // Inherited items can be only inside class context if (!$context instanceof ReflectionClass) { diff --git a/src/Aop/Pointcut/ModifierPointcut.php b/src/Aop/Pointcut/ModifierPointcut.php index c6953e0d..97db3767 100644 --- a/src/Aop/Pointcut/ModifierPointcut.php +++ b/src/Aop/Pointcut/ModifierPointcut.php @@ -55,8 +55,8 @@ public function __construct(int $initialMask = 0) public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { // With context only we always match, as we don't know about modifiers of given reflector if (!isset($reflector)) { diff --git a/src/Aop/Pointcut/NamePointcut.php b/src/Aop/Pointcut/NamePointcut.php index 75549271..a576bdcf 100644 --- a/src/Aop/Pointcut/NamePointcut.php +++ b/src/Aop/Pointcut/NamePointcut.php @@ -55,8 +55,8 @@ public function __construct( public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { // Let's determine what will be used for matching - context or reflector if ($this->useContextForMatching) { diff --git a/src/Aop/Pointcut/NotPointcut.php b/src/Aop/Pointcut/NotPointcut.php index 3453f6d7..71917c65 100644 --- a/src/Aop/Pointcut/NotPointcut.php +++ b/src/Aop/Pointcut/NotPointcut.php @@ -35,8 +35,8 @@ public function __construct(private Pointcut $pointcut) {} public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { // For Logical "not" expression without reflector, we should match statically for any context if (!isset($reflector)) { diff --git a/src/Aop/Pointcut/OrPointcut.php b/src/Aop/Pointcut/OrPointcut.php index 57933eba..7dc9f509 100644 --- a/src/Aop/Pointcut/OrPointcut.php +++ b/src/Aop/Pointcut/OrPointcut.php @@ -52,8 +52,8 @@ public function __construct(Pointcut ...$pointcuts) public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { foreach ($this->pointcuts as $singlePointcut) { if ($singlePointcut->matches($context, $reflector, $instanceOrScope, $arguments)) { diff --git a/src/Aop/Pointcut/PointcutReference.php b/src/Aop/Pointcut/PointcutReference.php index bf04c01a..33c46036 100644 --- a/src/Aop/Pointcut/PointcutReference.php +++ b/src/Aop/Pointcut/PointcutReference.php @@ -42,8 +42,8 @@ public function __construct( public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { return $this->getPointcut()->matches($context, $reflector, $instanceOrScope, $arguments); } diff --git a/src/Aop/Pointcut/ReturnTypePointcut.php b/src/Aop/Pointcut/ReturnTypePointcut.php index 0479ee97..7f141e9c 100644 --- a/src/Aop/Pointcut/ReturnTypePointcut.php +++ b/src/Aop/Pointcut/ReturnTypePointcut.php @@ -61,8 +61,8 @@ public function __construct(string $returnTypeName) public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): bool { // With only static context we always match, as we don't have any information about concrete reflector if (!isset($reflector)) { diff --git a/src/Aop/Pointcut/TruePointcut.php b/src/Aop/Pointcut/TruePointcut.php index 7f5eb9c7..4253b3ad 100644 --- a/src/Aop/Pointcut/TruePointcut.php +++ b/src/Aop/Pointcut/TruePointcut.php @@ -36,8 +36,8 @@ public function __construct(private int $pointcutKind = self::KIND_ALL) {} public function matches( ReflectionClass|ReflectionFileNamespace $context, ReflectionMethod|ReflectionProperty|ReflectionFunction|null $reflector = null, - object|string $instanceOrScope = null, - array $arguments = null + null|object|string $instanceOrScope = null, + ?array $arguments = null ): true { return true; } diff --git a/src/Instrument/ClassLoading/CachePathManager.php b/src/Instrument/ClassLoading/CachePathManager.php index 5d8dd040..a0438bd2 100644 --- a/src/Instrument/ClassLoading/CachePathManager.php +++ b/src/Instrument/ClassLoading/CachePathManager.php @@ -121,7 +121,7 @@ public function getCachePathForResource(string $resource) * * @return array|null Information or null if no record in the cache */ - public function queryCacheState(string $resource = null): ?array + public function queryCacheState(?string $resource = null): ?array { if ($resource === null) { return $this->cacheState; diff --git a/src/Instrument/Transformer/StreamMetaData.php b/src/Instrument/Transformer/StreamMetaData.php index 197bd182..b9ac030e 100644 --- a/src/Instrument/Transformer/StreamMetaData.php +++ b/src/Instrument/Transformer/StreamMetaData.php @@ -85,7 +85,7 @@ class StreamMetaData * @param string $source Source code or null * @throws InvalidArgumentException for invalid stream */ - public function __construct($stream, string $source = null) + public function __construct($stream, ?string $source = null) { if (!is_resource($stream)) { throw new InvalidArgumentException('Stream should be valid resource'); diff --git a/src/Instrument/Transformer/WeavingTransformer.php b/src/Instrument/Transformer/WeavingTransformer.php index b7dcffba..988adbb1 100644 --- a/src/Instrument/Transformer/WeavingTransformer.php +++ b/src/Instrument/Transformer/WeavingTransformer.php @@ -102,10 +102,10 @@ public function transform(StreamMetaData $metadata): TransformerResultEnum $class, $parsedSource->isStrictMode() ); - $totalTransformations += (integer) $wasClassProcessed; + $totalTransformations += (int) $wasClassProcessed; } $wasFunctionsProcessed = $this->processFunctions($advisors, $metadata, $namespace); - $totalTransformations += (integer) $wasFunctionsProcessed; + $totalTransformations += (int) $wasFunctionsProcessed; } $result = ($totalTransformations > 0) ? TransformerResultEnum::RESULT_TRANSFORMED : TransformerResultEnum::RESULT_ABSTAIN; @@ -207,7 +207,7 @@ private function adjustOriginalClass( } while (true); foreach ($class->getMethods(ReflectionMethod::IS_FINAL) as $finalMethod) { - if (!$finalMethod instanceof ReflectionMethod || $finalMethod->getDeclaringClass()->name !== $class->name) { + if ($finalMethod->getDeclaringClass()->name !== $class->name) { continue; } $hasDynamicAdvice = isset($advices[AspectContainer::METHOD_PREFIX][$finalMethod->name]); diff --git a/src/Proxy/ClassProxyGenerator.php b/src/Proxy/ClassProxyGenerator.php index ed904c82..f312fd91 100644 --- a/src/Proxy/ClassProxyGenerator.php +++ b/src/Proxy/ClassProxyGenerator.php @@ -127,7 +127,7 @@ public function __construct( /** * Adds use alias for this class */ - public function addUse(string $use, string $useAlias = null): void + public function addUse(string $use, ?string $useAlias = null): void { $this->generator->addUse($use, $useAlias); } diff --git a/src/Proxy/Part/PropertyInterceptionTrait.php b/src/Proxy/Part/PropertyInterceptionTrait.php index d3fda90c..8ec1a3cb 100644 --- a/src/Proxy/Part/PropertyInterceptionTrait.php +++ b/src/Proxy/Part/PropertyInterceptionTrait.php @@ -40,7 +40,7 @@ public function &__get(string $name) $fieldAccess->ensureScopeRule(); $value = &$fieldAccess->__invoke($this, FieldAccessType::READ, $this->__properties[$name]); - } elseif (method_exists(get_parent_class(), __FUNCTION__)) { + } elseif (method_exists(get_parent_class($this), __FUNCTION__)) { $value = parent::__get($name); } else { trigger_error("Trying to access undeclared property {$name}"); @@ -67,7 +67,7 @@ public function __set(string $name, $value) $this->__properties[$name], $value ); - } elseif (method_exists(get_parent_class(), __FUNCTION__)) { + } elseif (method_exists(get_parent_class($this), __FUNCTION__)) { parent::__set($name, $value); } else { $this->$name = $value; diff --git a/tests/Go/Aop/Framework/AbstractJoinpointTest.php b/tests/Go/Aop/Framework/AbstractJoinpointTest.php index 06b638a4..0454bfd6 100644 --- a/tests/Go/Aop/Framework/AbstractJoinpointTest.php +++ b/tests/Go/Aop/Framework/AbstractJoinpointTest.php @@ -28,114 +28,68 @@ public function testSortingLogic(array $advices, array $order = []): void } } - public function sortingTestSource(): array + public static function sortingTestSource(): array { + $after = new class implements AdviceAfter {}; + $before = new class implements AdviceBefore {}; + $around = new class implements AdviceAround {}; + + $forth = self::makeOrderedAdvice(4); + $first = self::makeOrderedAdvice(1); + return [ // #0 [ - [ - $this->createMock(AdviceAfter::class), - $this->createMock(AdviceBefore::class) - ], - [ - AdviceBefore::class, - AdviceAfter::class - ] + [clone $after, clone $before], + [AdviceBefore::class, AdviceAfter::class] ], // #1 [ - [ - $this->createMock(AdviceAfter::class), - $this->createMock(AdviceAround::class) - ], - [ - AdviceAfter::class, - AdviceAround::class - ] + [clone $after, clone $around], + [AdviceAfter::class, AdviceAround::class] ], // #2 [ - [ - $this->createMock(AdviceBefore::class), - $this->createMock(AdviceAfter::class) - ], - [ - AdviceBefore::class, - AdviceAfter::class - ] + [clone $before, clone $after], + [AdviceBefore::class, AdviceAfter::class] ], // #3 [ - [ - $this->createMock(AdviceBefore::class), - $this->createMock(AdviceAround::class) - ], - [ - AdviceBefore::class, - AdviceAround::class - ] + [clone $before, clone $around], + [AdviceBefore::class, AdviceAround::class] ], // #4 [ - [ - $this->createMock(AdviceAround::class), - $this->createMock(AdviceAfter::class) - ], - [ - AdviceAfter::class, - AdviceAround::class - ] + [clone $around, clone $after], + [AdviceAfter::class, AdviceAround::class] ], // #5 [ - [ - $this->createMock(AdviceAround::class), - $this->createMock(AdviceBefore::class) - ], - [ - AdviceBefore::class, - AdviceAround::class - ] + [clone $around, clone $before], + [AdviceBefore::class, AdviceAround::class] ], // #6 [ - [ - $this->createMock(AdviceBefore::class), - $this->createMock(AdviceAround::class), - $this->createMock(AdviceBefore::class), - $this->createMock(AdviceAfter::class), - ], - [ - AdviceBefore::class, - AdviceBefore::class, - AdviceAfter::class, - AdviceAround::class, - ] + [clone $before, clone $around, clone $before, clone $after], + [AdviceBefore::class, AdviceBefore::class, AdviceAfter::class, AdviceAround::class] ], // #7 [ - [ - $forth = $this->getOrderedAdvice(4), - $first = $this->getOrderedAdvice(1) - ], - [ - get_class($first), - get_class($forth), - ] + [$forth, $first], + [get_class($first), get_class($forth)] ], ]; } - /** - * Returns the ordered advice - */ - private function getOrderedAdvice(int $order): OrderedAdvice + private static function makeOrderedAdvice(int $order): OrderedAdvice { - $mock = $this->createMock(OrderedAdvice::class); - $mock - ->method('getAdviceOrder') - ->willReturn($order); + return new class($order) implements OrderedAdvice { + public function __construct(private readonly int $order) {} - return $mock; + public function getAdviceOrder(): int + { + return $this->order; + } + }; } } diff --git a/tests/Go/Aop/Framework/BaseInterceptorTest.php b/tests/Go/Aop/Framework/BaseInterceptorTest.php index 7685963b..ecb245d9 100644 --- a/tests/Go/Aop/Framework/BaseInterceptorTest.php +++ b/tests/Go/Aop/Framework/BaseInterceptorTest.php @@ -40,10 +40,12 @@ public function testCanSerializeInterceptor() $advice = $this->getAdvice($sequence); $mock = new AbstractInterceptorMock($advice); - $mockClass = get_class($mock); - $mockNameLength = strlen($mockClass); - $result = serialize($mock); - $expected = 'O:' . $mockNameLength . ':"' . $mockClass . '":1:{s:12:"adviceMethod";a:2:{s:4:"name";s:26:"Go\Aop\Framework\{closure}";s:5:"class";s:44:"Go\Aop\Framework\AbstractInterceptorTestCase";}}'; + $mockClass = get_class($mock); + $mockNameLength = strlen($mockClass); + $closureName = (new \ReflectionFunction($advice))->getName(); + $closureNameLen = strlen($closureName); + $result = serialize($mock); + $expected = 'O:' . $mockNameLength . ':"' . $mockClass . '":1:{s:12:"adviceMethod";a:2:{s:4:"name";s:' . $closureNameLen . ':"' . $closureName . '";s:5:"class";s:44:"Go\Aop\Framework\AbstractInterceptorTestCase";}}'; $this->assertEquals($expected, $result); } diff --git a/tests/Go/Core/ContainerTest.php b/tests/Go/Core/ContainerTest.php index 965e2054..a6893414 100644 --- a/tests/Go/Core/ContainerTest.php +++ b/tests/Go/Core/ContainerTest.php @@ -124,7 +124,7 @@ public function testResourceManagement(): void $isFresh = $this->container->hasAnyResourceChangedSince($realMtime - 3600); $this->assertFalse($isFresh); - $isFresh = $this->container->hasAnyResourceChangedSince($realMtime + 3600); + $isFresh = $this->container->hasAnyResourceChangedSince(time() + 3600); $this->assertTrue($isFresh); } diff --git a/tests/Go/Instrument/Transformer/_files/php7-class-proxy.php b/tests/Go/Instrument/Transformer/_files/php7-class-proxy.php index 56d011b8..55c6e38d 100644 --- a/tests/Go/Instrument/Transformer/_files/php7-class-proxy.php +++ b/tests/Go/Instrument/Transformer/_files/php7-class-proxy.php @@ -93,31 +93,31 @@ public function exceptionArg(\Exception $exception, \Test\ns1\Exception $localEx { return self::$__joinPoints['method:exceptionArg']->__invoke($this, [$exception, $localException]); } - public function stringRth(string $arg) : string + public function stringRth(string $arg): string { return self::$__joinPoints['method:stringRth']->__invoke($this, [$arg]); } - public function floatRth(float $arg) : float + public function floatRth(float $arg): float { return self::$__joinPoints['method:floatRth']->__invoke($this, [$arg]); } - public function boolRth(bool $arg) : bool + public function boolRth(bool $arg): bool { return self::$__joinPoints['method:boolRth']->__invoke($this, [$arg]); } - public function intRth(int $arg) : int + public function intRth(int $arg): int { return self::$__joinPoints['method:intRth']->__invoke($this, [$arg]); } - public function callableRth(callable $arg) : callable + public function callableRth(callable $arg): callable { return self::$__joinPoints['method:callableRth']->__invoke($this, [$arg]); } - public function arrayRth(array $arg) : array + public function arrayRth(array $arg): array { return self::$__joinPoints['method:arrayRth']->__invoke($this, [$arg]); } - public function exceptionRth(\Exception $exception) : \Exception + public function exceptionRth(\Exception $exception): \Exception { return self::$__joinPoints['method:exceptionRth']->__invoke($this, [$exception]); } diff --git a/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php b/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php index f26e5b32..45774b39 100644 --- a/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php +++ b/tests/Go/Proxy/Part/InterceptedMethodGeneratorTest.php @@ -52,17 +52,17 @@ public static function dataGenerator(): array [ First::class, 'variadicArgsTest', - 'public function variadicArgsTest(... $args) : string' + 'public function variadicArgsTest(... $args): string' ], [ First::class, 'staticLsbRecursion', - 'public static function staticLsbRecursion(int $value, int $level = 0) : int' + 'public static function staticLsbRecursion(int $value, int $level = 0): int' ], [ First::class, 'staticLsbProtected', - 'protected static function staticLsbProtected() : string' + 'protected static function staticLsbProtected(): string' ], [ First::class, @@ -72,7 +72,7 @@ public static function dataGenerator(): array [ First::class, 'privateMethod', - 'private function privateMethod() : int' + 'private function privateMethod(): int' ], ]; }