Skip to content

Commit 6fe0f8a

Browse files
lisachenkoclaude
andcommitted
feat(proxy): pass advices as argument to injectJoinPoints instead of property
Closes #518 Previously, generated proxy classes embedded advisor name strings directly in the \$__joinPoints property initializer. ClassProxyGenerator::injectJoinPoints() would then read that property via reflection, transform the strings into Joinpoint objects, and write them back — mutating the property's type at runtime. Now the \$__joinPoints property is always declared empty (private static array \$__joinPoints = []) and the full advice names array is passed as a second argument to the injectJoinPoints() call at the bottom of the generated proxy file. This makes the initialization call self-contained and removes the reflection read step. Updated ProxyClassReflectionHelper::extractAdvicesFromProxyFile() replaces the old getStaticPropertyValue('__joinPoints') approach in test constraints so they read advisor names from the injectJoinPoints() call in the proxy file. Also corrected the wrapWithJoinPoints() docblock: the parameter is string[][][] (advisor name strings), not Advice[][][], and removed the now-obsolete PHPStan baseline entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4148e23 commit 6fe0f8a

File tree

10 files changed

+148
-135
lines changed

10 files changed

+148
-135
lines changed

phpstan-baseline.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,5 @@
3030
'count' => 1,
3131
'path' => __DIR__ . '/src/Proxy/ClassProxyGenerator.php',
3232
];
33-
$ignoreErrors[] = [
34-
// The __joinPoints property is generated by code generation at weave time and populated with
35-
// the proper array<array<array<Advice>>> structure. ReflectionProperty::getValue() returns mixed
36-
// because PHP reflection cannot know the type of dynamically generated properties.
37-
'message' => '#^Parameter \#1 \$classAdvices of static method Go\\\\Proxy\\\\ClassProxyGenerator::wrapWithJoinPoints\(\) expects array\<array\<array\<Go\\\\Aop\\\\Advice\>\>\>, mixed given\.$#',
38-
'identifier' => 'argument.type',
39-
'count' => 1,
40-
'path' => __DIR__ . '/src/Proxy/ClassProxyGenerator.php',
41-
];
4233

4334
return ['parameters' => ['ignoreErrors' => $ignoreErrors]];

src/Proxy/ClassProxyGenerator.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Go\Proxy\Part\PropertyInterceptionTrait;
3131
use Laminas\Code\Generator\ClassGenerator;
3232
use Laminas\Code\Generator\DocBlockGenerator;
33+
use Laminas\Code\Generator\ValueGenerator;
3334
use Laminas\Code\Reflection\DocBlockReflection;
3435
use ReflectionClass;
3536
use ReflectionMethod;
@@ -96,7 +97,7 @@ public function __construct(
9697
$introducedInterfaces = $classAdviceNames[AspectContainer::INTRODUCTION_INTERFACE_PREFIX]['root'] ?? [];
9798
$introducedTraits = $classAdviceNames[AspectContainer::INTRODUCTION_TRAIT_PREFIX]['root'] ?? [];
9899

99-
$generatedProperties = [new JoinPointPropertyGenerator($classAdviceNames)];
100+
$generatedProperties = [new JoinPointPropertyGenerator()];
100101
$generatedMethods = $this->interceptMethods($originalClass, $interceptedMethods);
101102

102103
$introducedInterfaces[] = '\\' . Proxy::class;
@@ -142,16 +143,17 @@ public function addUse(string $use, ?string $useAlias = null): void
142143
* Inject advices into given class
143144
*
144145
* NB This method will be used as a callback during source code evaluation to inject joinpoints
146+
*
147+
* @param string[][][] $advices List of advices to inject
145148
*/
146-
public static function injectJoinPoints(string $targetClassName): void
149+
public static function injectJoinPoints(string $targetClassName, array $advices = []): void
147150
{
148151
if (!class_exists($targetClassName)) {
149152
return;
150153
}
151154
$reflectionClass = new ReflectionClass($targetClassName);
152155
$joinPointsProperty = $reflectionClass->getProperty(JoinPointPropertyGenerator::NAME);
153156

154-
$advices = $joinPointsProperty->getValue();
155157
$joinPoints = static::wrapWithJoinPoints($advices, $reflectionClass->name);
156158
$joinPointsProperty->setValue(null, $joinPoints);
157159

@@ -166,17 +168,18 @@ public static function injectJoinPoints(string $targetClassName): void
166168
*/
167169
public function generate(): string
168170
{
169-
$classCode = $this->generator->generate();
171+
$classCode = $this->generator->generate();
172+
$advicesValue = new ValueGenerator($this->adviceNames, ValueGenerator::TYPE_ARRAY_SHORT);
170173

171174
return $classCode
172175
// Inject advices on call
173-
. '\\' . self::class . '::injectJoinPoints(' . $this->generator->getName() . '::class);';
176+
. '\\' . self::class . '::injectJoinPoints(' . $this->generator->getName() . '::class, ' . $advicesValue->generate() . ');';
174177
}
175178

176179
/**
177180
* Wrap advices with joinpoint object
178181
*
179-
* @param array|Advice[][][] $classAdvices Advices for specific class
182+
* @param string[][][] $classAdvices Advisor name strings indexed by join point type and name
180183
*
181184
* @throws UnexpectedValueException If joinPoint type is unknown
182185
*

src/Proxy/Part/JoinPointPropertyGenerator.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Laminas\Code\Generator\Exception\InvalidArgumentException;
1616
use Laminas\Code\Generator\PropertyGenerator;
1717
use Laminas\Code\Generator\PropertyValueGenerator;
18+
use Laminas\Code\Generator\TypeGenerator;
1819

1920
/**
2021
* Prepares the definition for joinpoints private property in the class
@@ -29,16 +30,15 @@ final class JoinPointPropertyGenerator extends PropertyGenerator
2930
/**
3031
* JoinPointProperty constructor.
3132
*
32-
* @param string[][][] $adviceNames List of advices to apply per class
33-
*
3433
* @throws InvalidArgumentException
3534
*/
36-
public function __construct(array $adviceNames)
35+
public function __construct()
3736
{
38-
$value = new PropertyValueGenerator($adviceNames, PropertyValueGenerator::TYPE_ARRAY_SHORT);
37+
$value = new PropertyValueGenerator([], PropertyValueGenerator::TYPE_ARRAY_SHORT);
3938

4039
parent::__construct(self::NAME, $value, PropertyGenerator::FLAG_PRIVATE | PropertyGenerator::FLAG_STATIC);
4140

41+
$this->setType(TypeGenerator::fromTypeString('array'));
4242
$this->setDocBlock('List of applied advices per class');
4343
}
4444
}

tests/Go/Instrument/Transformer/_files/class-proxy.php

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,7 @@ class TestClass extends TestClass__AopProxied implements \Go\Aop\Proxy
77
/**
88
* List of applied advices per class
99
*/
10-
private static $__joinPoints = [
11-
'method' => [
12-
'publicMethod' => [
13-
'advisor.Test\\ns1\\TestClass->publicMethod',
14-
],
15-
'protectedMethod' => [
16-
'advisor.Test\\ns1\\TestClass->protectedMethod',
17-
],
18-
'publicStaticMethod' => [
19-
'advisor.Test\\ns1\\TestClass->publicStaticMethod',
20-
],
21-
'protectedStaticMethod' => [
22-
'advisor.Test\\ns1\\TestClass->protectedStaticMethod',
23-
],
24-
'publicMethodDynamicArguments' => [
25-
'advisor.Test\\ns1\\TestClass->publicMethodDynamicArguments',
26-
],
27-
'publicMethodFixedArguments' => [
28-
'advisor.Test\\ns1\\TestClass->publicMethodFixedArguments',
29-
],
30-
'methodWithSpecialTypeArguments' => [
31-
'advisor.Test\\ns1\\TestClass->methodWithSpecialTypeArguments',
32-
],
33-
],
10+
private static array $__joinPoints = [
3411
];
3512

3613
public function publicMethod()
@@ -72,4 +49,28 @@ public function methodWithSpecialTypeArguments($instance)
7249
return self::$__joinPoints['method:methodWithSpecialTypeArguments']->__invoke($this, [$instance]);
7350
}
7451
}
75-
\Go\Proxy\ClassProxyGenerator::injectJoinPoints(TestClass::class);
52+
\Go\Proxy\ClassProxyGenerator::injectJoinPoints(TestClass::class, [
53+
'method' => [
54+
'publicMethod' => [
55+
'advisor.Test\\ns1\\TestClass->publicMethod',
56+
],
57+
'protectedMethod' => [
58+
'advisor.Test\\ns1\\TestClass->protectedMethod',
59+
],
60+
'publicStaticMethod' => [
61+
'advisor.Test\\ns1\\TestClass->publicStaticMethod',
62+
],
63+
'protectedStaticMethod' => [
64+
'advisor.Test\\ns1\\TestClass->protectedStaticMethod',
65+
],
66+
'publicMethodDynamicArguments' => [
67+
'advisor.Test\\ns1\\TestClass->publicMethodDynamicArguments',
68+
],
69+
'publicMethodFixedArguments' => [
70+
'advisor.Test\\ns1\\TestClass->publicMethodFixedArguments',
71+
],
72+
'methodWithSpecialTypeArguments' => [
73+
'advisor.Test\\ns1\\TestClass->methodWithSpecialTypeArguments',
74+
],
75+
],
76+
]);

tests/Go/Instrument/Transformer/_files/php7-class-proxy.php

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,60 +6,7 @@ class TestPhp7Class extends TestPhp7Class__AopProxied implements \Go\Aop\Proxy
66
/**
77
* List of applied advices per class
88
*/
9-
private static $__joinPoints = [
10-
'method' => [
11-
'stringSth' => [
12-
'advisor.Test\\ns1\\TestPhp7Class->stringSth',
13-
],
14-
'floatSth' => [
15-
'advisor.Test\\ns1\\TestPhp7Class->floatSth',
16-
],
17-
'boolSth' => [
18-
'advisor.Test\\ns1\\TestPhp7Class->boolSth',
19-
],
20-
'intSth' => [
21-
'advisor.Test\\ns1\\TestPhp7Class->intSth',
22-
],
23-
'callableSth' => [
24-
'advisor.Test\\ns1\\TestPhp7Class->callableSth',
25-
],
26-
'arraySth' => [
27-
'advisor.Test\\ns1\\TestPhp7Class->arraySth',
28-
],
29-
'variadicStringSthByRef' => [
30-
'advisor.Test\\ns1\\TestPhp7Class->variadicStringSthByRef',
31-
],
32-
'exceptionArg' => [
33-
'advisor.Test\\ns1\\TestPhp7Class->exceptionArg',
34-
],
35-
'stringRth' => [
36-
'advisor.Test\\ns1\\TestPhp7Class->stringRth',
37-
],
38-
'floatRth' => [
39-
'advisor.Test\\ns1\\TestPhp7Class->floatRth',
40-
],
41-
'boolRth' => [
42-
'advisor.Test\\ns1\\TestPhp7Class->boolRth',
43-
],
44-
'intRth' => [
45-
'advisor.Test\\ns1\\TestPhp7Class->intRth',
46-
],
47-
'callableRth' => [
48-
'advisor.Test\\ns1\\TestPhp7Class->callableRth',
49-
],
50-
'arrayRth' => [
51-
'advisor.Test\\ns1\\TestPhp7Class->arrayRth',
52-
],
53-
'exceptionRth' => [
54-
'advisor.Test\\ns1\\TestPhp7Class->exceptionRth',
55-
],
56-
'noRth' => [
57-
'advisor.Test\\ns1\\TestPhp7Class->noRth',
58-
],
59-
'returnSelf' => [
60-
'advisor.Test\\ns1\\TestPhp7Class->returnSelf',
61-
],
62-
],
9+
private static array $__joinPoints = [
6310
];
6411
public function stringSth(string $arg)
6512
{
@@ -134,4 +81,58 @@ public function returnSelf()
13481
return self::$__joinPoints['method:returnSelf']->__invoke($this);
13582
}
13683
}
137-
\Go\Proxy\ClassProxyGenerator::injectJoinPoints(TestPhp7Class::class);
84+
\Go\Proxy\ClassProxyGenerator::injectJoinPoints(TestPhp7Class::class, [
85+
'method' => [
86+
'stringSth' => [
87+
'advisor.Test\\ns1\\TestPhp7Class->stringSth',
88+
],
89+
'floatSth' => [
90+
'advisor.Test\\ns1\\TestPhp7Class->floatSth',
91+
],
92+
'boolSth' => [
93+
'advisor.Test\\ns1\\TestPhp7Class->boolSth',
94+
],
95+
'intSth' => [
96+
'advisor.Test\\ns1\\TestPhp7Class->intSth',
97+
],
98+
'callableSth' => [
99+
'advisor.Test\\ns1\\TestPhp7Class->callableSth',
100+
],
101+
'arraySth' => [
102+
'advisor.Test\\ns1\\TestPhp7Class->arraySth',
103+
],
104+
'variadicStringSthByRef' => [
105+
'advisor.Test\\ns1\\TestPhp7Class->variadicStringSthByRef',
106+
],
107+
'exceptionArg' => [
108+
'advisor.Test\\ns1\\TestPhp7Class->exceptionArg',
109+
],
110+
'stringRth' => [
111+
'advisor.Test\\ns1\\TestPhp7Class->stringRth',
112+
],
113+
'floatRth' => [
114+
'advisor.Test\\ns1\\TestPhp7Class->floatRth',
115+
],
116+
'boolRth' => [
117+
'advisor.Test\\ns1\\TestPhp7Class->boolRth',
118+
],
119+
'intRth' => [
120+
'advisor.Test\\ns1\\TestPhp7Class->intRth',
121+
],
122+
'callableRth' => [
123+
'advisor.Test\\ns1\\TestPhp7Class->callableRth',
124+
],
125+
'arrayRth' => [
126+
'advisor.Test\\ns1\\TestPhp7Class->arrayRth',
127+
],
128+
'exceptionRth' => [
129+
'advisor.Test\\ns1\\TestPhp7Class->exceptionRth',
130+
],
131+
'noRth' => [
132+
'advisor.Test\\ns1\\TestPhp7Class->noRth',
133+
],
134+
'returnSelf' => [
135+
'advisor.Test\\ns1\\TestPhp7Class->returnSelf',
136+
],
137+
],
138+
]);

tests/Go/PhpUnit/ClassMemberNotWovenConstraint.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ public function matches($other): bool
3636
throw new InvalidArgumentException(sprintf('Expected instance of "%s", got "%s".', ClassAdvisorIdentifier::class, is_object($other) ? get_class($other) : gettype($other)));
3737
}
3838

39-
$reflectionClass = ProxyClassReflectionHelper::createReflectionClass($other->getClass(), $this->configuration);
40-
$wovenAdvisorIdentifiers = $reflectionClass->getStaticPropertyValue('__joinPoints', null);
39+
$wovenAdvisorIdentifiers = ProxyClassReflectionHelper::extractAdvicesFromProxyFile($other->getClass(), $this->configuration);
4140
$target = $other->getTarget();
4241

43-
if (null === $wovenAdvisorIdentifiers) { // there are no advisor identifiers
42+
if ([] === $wovenAdvisorIdentifiers) { // there are no advisor identifiers
4443
return true;
4544
}
4645

tests/Go/PhpUnit/ClassMemberWovenConstraint.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@ public function matches($other): bool
3535
throw new \InvalidArgumentException(sprintf('Expected instance of "%s", got "%s".', ClassAdvisorIdentifier::class, is_object($other) ? get_class($other) : gettype($other)));
3636
}
3737

38-
$reflectionClass = ProxyClassReflectionHelper::createReflectionClass($other->getClass(), $this->configuration);
39-
$wovenAdvisorIdentifiers = $reflectionClass->getStaticPropertyValue('__joinPoints', null);
38+
$wovenAdvisorIdentifiers = ProxyClassReflectionHelper::extractAdvicesFromProxyFile($other->getClass(), $this->configuration);
4039
$target = $other->getTarget();
4140

42-
if (null === $wovenAdvisorIdentifiers) { // there are no advisor identifiers
41+
if ([] === $wovenAdvisorIdentifiers) { // there are no advisor identifiers
4342
return false;
4443
}
4544

tests/Go/PhpUnit/ProxyClassReflectionHelper.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,52 @@ private function __construct()
2727
{
2828
}
2929

30+
/**
31+
* Extracts the advice names array from the injectJoinPoints() call in the generated proxy file.
32+
*
33+
* @param string $className Full qualified class name
34+
* @param array $configuration Configuration used for Go! AOP project setup
35+
*
36+
* @return string[][][] Advice names indexed by join point type and name, or empty array if not found
37+
*/
38+
public static function extractAdvicesFromProxyFile(string $className, array $configuration): array
39+
{
40+
$parsedReflectionClass = new ReflectionClass($className);
41+
$originalClassFile = $parsedReflectionClass->getFileName();
42+
43+
$appDir = PathResolver::realpath($configuration['appDir']);
44+
$relativePath = str_replace($appDir . DIRECTORY_SEPARATOR, '', $originalClassFile);
45+
$classSuffix = str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';
46+
$proxyRelativePath = $relativePath . DIRECTORY_SEPARATOR . $classSuffix;
47+
$proxyFileName = $configuration['cacheDir'] . '/_proxies/' . $proxyRelativePath;
48+
49+
if (!file_exists($proxyFileName)) {
50+
return [];
51+
}
52+
53+
$proxyFileContent = file_get_contents($proxyFileName);
54+
$pos = strrpos($proxyFileContent, '::injectJoinPoints(');
55+
56+
if ($pos === false) {
57+
return [];
58+
}
59+
60+
$callStr = substr($proxyFileContent, $pos);
61+
$commaPos = strpos($callStr, ',');
62+
63+
if ($commaPos === false) {
64+
return [];
65+
}
66+
67+
$arrayStr = trim(substr($callStr, $commaPos + 1));
68+
69+
if (str_ends_with(rtrim($arrayStr), ');')) {
70+
$arrayStr = substr(rtrim($arrayStr), 0, -2);
71+
}
72+
73+
return eval('return ' . $arrayStr . ';');
74+
}
75+
3076
/**
3177
* Creates \Go\ParserReflection\ReflectionClass instance that introspects class without loading class into memory.
3278
*

tests/Go/Proxy/ClassProxyGeneratorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public function testGenerateProxyMethod(string $className, string $methodName):
7171
$proxyHasJoinpointProperty = $proxyClass->hasProperty(JoinPointPropertyGenerator::NAME);
7272
$this->assertTrue($proxyHasJoinpointProperty, 'Child should have joinpoint property in it');
7373
$joinPoints = $proxyClass->getStaticPropertyValue(JoinPointPropertyGenerator::NAME);
74-
$this->assertSame($classAdvices, $joinPoints);
74+
$this->assertSame([], $joinPoints);
7575

7676
$this->assertTrue($proxyClass->hasMethod($methodName));
7777
$interceptedMethod = $proxyClass->getMethod($methodName);

0 commit comments

Comments
 (0)