Add native PHP 8.4 property-hook field interception for class and trait proxies#546
Conversation
lisachenko
left a comment
There was a problem hiding this comment.
@copilot please read and fix comments
private string $privateProperty {
get {
/** @var \Go\Aop\Intercept\FieldAccess<self,string> $fieldAccess */
$fieldAccess = self::$__joinPoints['prop:privateProperty'];
if ($fieldAccess->getField()->isInitialized()) {
$value =& $fieldAccess->__invoke($this, \Go\Aop\Intercept\FieldAccessType::READ, $this->privateProperty);
return $value;
} else {
return $fieldAccess->__invoke($this, \Go\Aop\Intercept\FieldAccessType::READ);
}
}
}same logic needed for setters as well. If property has default value - then we can keep existing template.
return $fieldAccess->__invoke($this, \Go\Aop\Intercept\FieldAccessType::READ, $this->value);
|
Implemented in 9a1428c. Added README docs for PHP 8.4 property interception (including inherited public/protected behavior and unsupported cases), added tests for parent property interception and final-parent-property exclusion, and added uninitialized typed-property get-hook safeguard generation with |
lisachenko
left a comment
There was a problem hiding this comment.
@copilot fix review comments
|
This still doesn't work for traits:
|
Implemented in |
Implemented in |
|
@copilot fix broken tests after my recent changes. Also, try to extract similar boilerplate code from InterceptedPropertyGenerator + TraitInterceptedPropertyGenerator either into common abstract parent class or into separate helper class. |
Fixed in |
|
@copilot everything looks good, the only small remaining thing - if property uses a PHP class type, then typehint may be incorrect, check this: private \Exception $privateProperty;becomes private \Exception $privateProperty {
get {
/** @var \Go\Aop\Intercept\FieldAccess<self, Exception> $fieldAccess */
$fieldAccess = self::$__joinPoints['prop:privateProperty'];
if ($fieldAccess->getField()->isInitialized($this)) {
return $fieldAccess->__invoke($this, \Go\Aop\Intercept\FieldAccessType::READ, $this->privateProperty);
} else {
return $fieldAccess->__invoke($this, \Go\Aop\Intercept\FieldAccessType::READ);
}
}
set {
/** @var \Go\Aop\Intercept\FieldAccess<self, Exception> $fieldAccess */
$fieldAccess = self::$__joinPoints['prop:privateProperty'];
if ($fieldAccess->getField()->isInitialized($this)) {
$this->privateProperty = $fieldAccess->__invoke($this, \Go\Aop\Intercept\FieldAccessType::WRITE, $value, $this->privateProperty);
} else {
$this->privateProperty = $fieldAccess->__invoke($this, \Go\Aop\Intercept\FieldAccessType::WRITE, $value);
}
}and typehint is incorrect: /** @var \Go\Aop\Intercept\FieldAccess<self, Exception> $fieldAccess */it should have FQDN too: |
... Fixed in |
[Feature] Allow to intercept even parent protected/public properties [Feature] Add second template argument for FieldAccess to define type of property [Feature] Configured InterceptedPropertyGenerator to use property type for template argument <V> for better code-completion
[Feature] Allow interception of properties in traits, which is now possible with native PHP hooks
[BC Break] FieldAccess getValue() and getValueToSet() API not returning anymore value by reference. [BC Break] FieldAccess and ClassFieldAccess implementation now use chain of responsiblity and return values instead of void.
2a4bb7b to
3d66ec4
Compare
This change implements the PHP 8.4 property-hooks MVP for field interception in the trait-based engine. Intercepted properties are emitted as native hooked properties on generated proxies, while unsupported properties are intentionally excluded from advice matching.
Proxy property interception moved to native hooks
InterceptedPropertyGeneratorto generate intercepted properties with native hooks for class proxies.ClassProxyGeneratorre-declares intercepted properties on the proxy and routes access viaClassFieldAccessjoin points (prop:<name>keys).final, type/default value, attributes, and asymmetric set visibility (protected(set)/private(set))./** @var \Go\Aop\Intercept\FieldAccess<self,<type>> $fieldAccess */Trait property interception support
TraitInterceptedPropertyGeneratorto generate native hooked properties for trait proxies.TraitProxyGeneratornow emits intercepted trait fields as hooked properties and initializes join points lazily per hook viaTraitProxyGenerator::getJoinPoint(...).&get-only behavior used for class proxies.self::$__joinPoints[...]storage.Parent property interception support
finalproperties are skipped only when inherited from parent classes.finalproperties declared in the current class are supported.Uninitialized typed property safeguard
gethooks guard with:$fieldAccess->getField()->isInitialized($this)sethooks include the same initialization guard:WRITE, $this->prop, $value)WRITE, $value)getflow).ClassFieldAccesshandling supports both:Array property special-case for indirect modification
&getonly and intentionally omitssetto preserve indirect modifications (for examplearray_push($this->items, ...)).arrayare handled as array-backed for this behavior.Reflection-first flow and validation boundaries
ReflectionPropertyonly (no AST input inInterceptedPropertyGenerator/TraitInterceptedPropertyGenerator).AdviceMatcher; proxy generators only enforce generator-specific wiring constraints.static,readonly, already-hooked properties) are rejected explicitly.Woven trait property handling
WeavingTransformercomments out intercepted property declarations in woven bodies and appends relocation metadata:// Moved by weaving interceptor to the {@see Class->property}Field-access/runtime docs and cleanup
finalhandling.Test coverage updates
gethooks,sethooks,&get-only generation behavior,FieldAccessphpDoc in property hook bodies.Example of generated class proxy property shape (non-array, potentially uninitialized typed property):
Example for array-backed property: