- Extend Override attribute to support properties in addition to methods - Add comprehensive property override validation including parent property matching - Implement property shadowing prevention for private parent properties - Add trait property override validation at use site - Support promoted and hooked properties in override validation - Update error messages to include property targets for override rejection - Add internal marker preservation for property override validation - Implement final property and property hook inheritance restrictions - Add parent property hook call syntax support with validation - Support PHP 8.4 final property metadata preservation in reflection - Add property hook inheritance and reflection test cases - Update native class property validation to prevent private property hiding - Implement interface property hook final restriction - Add property get hook reference return validation error - Update stub generation with propertymaster
parent
4c59d9a21b
commit
36fd0228f0
32 changed files with 676 additions and 34 deletions
@ -0,0 +1,15 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class FinalHookedPropertyParent |
||||||
|
{ |
||||||
|
final public string $value { |
||||||
|
get => 'parent'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class FinalHookedPropertyChild extends FinalHookedPropertyParent |
||||||
|
{ |
||||||
|
public string $value { |
||||||
|
get => 'child'; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class FinalPropertyParent |
||||||
|
{ |
||||||
|
final public string $value = 'parent'; |
||||||
|
} |
||||||
|
|
||||||
|
class FinalPropertyChild extends FinalPropertyParent |
||||||
|
{ |
||||||
|
public string $value = 'child'; |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class FinalPropertyHookParent |
||||||
|
{ |
||||||
|
public string $value { |
||||||
|
final get => 'parent'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class FinalPropertyHookChild extends FinalPropertyHookParent |
||||||
|
{ |
||||||
|
public string $value { |
||||||
|
get => 'child'; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class PrivateSetPropertyParent |
||||||
|
{ |
||||||
|
public private(set) string $value = 'parent'; |
||||||
|
} |
||||||
|
|
||||||
|
class PrivateSetPropertyChild extends PrivateSetPropertyParent |
||||||
|
{ |
||||||
|
public string $value = 'child'; |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
interface FinalPropertyContract |
||||||
|
{ |
||||||
|
final public string $value { |
||||||
|
get; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
#[Native] |
||||||
|
class NativePrivateShadowParent |
||||||
|
{ |
||||||
|
private int $value = 1; |
||||||
|
} |
||||||
|
|
||||||
|
#[Native] |
||||||
|
class NativePrivateShadowChild extends NativePrivateShadowParent |
||||||
|
{ |
||||||
|
private int $value = 2; |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
interface OverridePropertyInterface |
||||||
|
{ |
||||||
|
#[\Override] |
||||||
|
public string $value { |
||||||
|
get; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class OverridePropertyMissing |
||||||
|
{ |
||||||
|
#[\Override] |
||||||
|
public string $value = 'missing'; |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class OverridePropertyPrivateParent |
||||||
|
{ |
||||||
|
private string $value = 'private'; |
||||||
|
} |
||||||
|
|
||||||
|
class OverridePropertyPrivateChild extends OverridePropertyPrivateParent |
||||||
|
{ |
||||||
|
#[\Override] |
||||||
|
public string $value = 'child'; |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
trait OverridePropertyMissingTrait |
||||||
|
{ |
||||||
|
#[\Override] |
||||||
|
public string $value = 'trait'; |
||||||
|
} |
||||||
|
|
||||||
|
class OverridePropertyTraitConsumer |
||||||
|
{ |
||||||
|
use OverridePropertyMissingTrait; |
||||||
|
} |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class OverridePropertyValidParent |
||||||
|
{ |
||||||
|
public string $plain = 'parent'; |
||||||
|
public string $promoted = 'parent'; |
||||||
|
|
||||||
|
public string $hooked { |
||||||
|
get => 'parent'; |
||||||
|
set { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class OverridePropertyValidChild extends OverridePropertyValidParent |
||||||
|
{ |
||||||
|
#[Override] |
||||||
|
public string $plain = 'child'; |
||||||
|
|
||||||
|
#[\Override] |
||||||
|
public string $hooked { |
||||||
|
get => 'child'; |
||||||
|
} |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
#[\Override] |
||||||
|
public string $promoted = 'child', |
||||||
|
) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
trait OverridePropertyValidTrait |
||||||
|
{ |
||||||
|
#[\Override] |
||||||
|
public string $plain = 'parent'; |
||||||
|
} |
||||||
|
|
||||||
|
class OverridePropertyValidTraitChild extends OverridePropertyValidParent |
||||||
|
{ |
||||||
|
use OverridePropertyValidTrait; |
||||||
|
} |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
--TEST-- |
||||||
|
PHP 8.4 final properties preserve runtime and reflection metadata |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class FinalPropertyMetadata |
||||||
|
{ |
||||||
|
final public string $plain = 'plain'; |
||||||
|
|
||||||
|
final public string $hooked { |
||||||
|
get => 'hooked'; |
||||||
|
set { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
final public string $finalHook { |
||||||
|
final get => 'both'; |
||||||
|
} |
||||||
|
|
||||||
|
public private(set) string $privateSet = 'private-set'; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$object = new FinalPropertyMetadata(); |
||||||
|
var_dump($object->plain, $object->hooked, $object->finalHook, $object->privateSet); |
||||||
|
|
||||||
|
foreach (['plain', 'hooked', 'finalHook', 'privateSet'] as $name) { |
||||||
|
$property = new ReflectionProperty(FinalPropertyMetadata::class, $name); |
||||||
|
echo $name, |
||||||
|
':final=', $property->isFinal() ? 'yes' : 'no', |
||||||
|
':hooks=', $property->hasHooks() ? 'yes' : 'no', |
||||||
|
':virtual=', $property->isVirtual() ? 'yes' : 'no', |
||||||
|
"\n"; |
||||||
|
foreach ($property->getHooks() as $kind => $hook) { |
||||||
|
echo $name, '-', $kind, ':', $hook->isFinal() ? 'final' : 'open', "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(5) "plain" |
||||||
|
string(6) "hooked" |
||||||
|
string(4) "both" |
||||||
|
string(11) "private-set" |
||||||
|
plain:final=yes:hooks=no:virtual=no |
||||||
|
hooked:final=yes:hooks=yes:virtual=yes |
||||||
|
hooked-get:open |
||||||
|
hooked-set:open |
||||||
|
finalHook:final=yes:hooks=yes:virtual=yes |
||||||
|
finalHook-get:final |
||||||
|
privateSet:final=yes:hooks=no:virtual=no |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
--TEST-- |
||||||
|
TypePHP Override attribute validates and is consumed from properties |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class OverridePropertyRuntimeParent |
||||||
|
{ |
||||||
|
public string $value = 'parent'; |
||||||
|
} |
||||||
|
|
||||||
|
class OverridePropertyRuntimeChild extends OverridePropertyRuntimeParent |
||||||
|
{ |
||||||
|
#[Override] |
||||||
|
public string $value = 'child'; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$object = new OverridePropertyRuntimeChild(); |
||||||
|
var_dump($object->value); |
||||||
|
|
||||||
|
$property = new ReflectionProperty(OverridePropertyRuntimeChild::class, 'value'); |
||||||
|
var_dump($property->getAttributes(\Override::class)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(5) "child" |
||||||
|
array(0) { |
||||||
|
} |
||||||
@ -0,0 +1,92 @@ |
|||||||
|
--TEST-- |
||||||
|
PHP 8.4 property hooks inherit and override get/set independently |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class ParentHook |
||||||
|
{ |
||||||
|
protected string $stored = ''; |
||||||
|
|
||||||
|
public string $value { |
||||||
|
get => 'parent:' . $this->stored; |
||||||
|
set { |
||||||
|
$this->stored = 'set:' . $value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function writeFromParent(string $value): void |
||||||
|
{ |
||||||
|
$this->value = $value; |
||||||
|
} |
||||||
|
|
||||||
|
public function readFromParent(): string |
||||||
|
{ |
||||||
|
return $this->value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ChildHook extends ParentHook |
||||||
|
{ |
||||||
|
public string $value { |
||||||
|
get => 'child:' . $this->stored; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class PlainHookChild extends ParentHook |
||||||
|
{ |
||||||
|
public string $value; |
||||||
|
} |
||||||
|
|
||||||
|
function writeHookDynamically(mixed $object, string $value): void |
||||||
|
{ |
||||||
|
$object->value = $value; |
||||||
|
} |
||||||
|
|
||||||
|
function readHookDynamically(mixed $object): string |
||||||
|
{ |
||||||
|
return $object->value; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$child = new ChildHook(); |
||||||
|
|
||||||
|
$child->value = 'direct'; |
||||||
|
var_dump($child->value, $child->readFromParent()); |
||||||
|
|
||||||
|
$child->writeFromParent('parent'); |
||||||
|
var_dump($child->value, $child->readFromParent()); |
||||||
|
|
||||||
|
writeHookDynamically($child, 'dynamic'); |
||||||
|
var_dump(readHookDynamically($child)); |
||||||
|
|
||||||
|
$property = new ReflectionProperty(ChildHook::class, 'value'); |
||||||
|
foreach ($property->getHooks() as $kind => $hook) { |
||||||
|
echo $kind, ':', $hook->getDeclaringClass()->getName(), ':', $hook->isFinal() ? 'final' : 'open', "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
$plain = new PlainHookChild(); |
||||||
|
$plain->value = 'plain'; |
||||||
|
var_dump($plain->value, $plain->readFromParent()); |
||||||
|
writeHookDynamically($plain, 'plain-dynamic'); |
||||||
|
var_dump(readHookDynamically($plain)); |
||||||
|
|
||||||
|
$plainProperty = new ReflectionProperty(PlainHookChild::class, 'value'); |
||||||
|
foreach ($plainProperty->getHooks() as $kind => $hook) { |
||||||
|
echo 'plain-', $kind, ':', $hook->getDeclaringClass()->getName(), "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(16) "child:set:direct" |
||||||
|
string(16) "child:set:direct" |
||||||
|
string(16) "child:set:parent" |
||||||
|
string(16) "child:set:parent" |
||||||
|
string(17) "child:set:dynamic" |
||||||
|
get:ChildHook:open |
||||||
|
set:ParentHook:open |
||||||
|
string(16) "parent:set:plain" |
||||||
|
string(16) "parent:set:plain" |
||||||
|
string(24) "parent:set:plain-dynamic" |
||||||
|
plain-get:ParentHook |
||||||
|
plain-set:ParentHook |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
--TEST-- |
||||||
|
Property hooks may call the corresponding parent get and set hook |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class ParentPropertyHookCall |
||||||
|
{ |
||||||
|
protected string $stored = ''; |
||||||
|
|
||||||
|
public string $value { |
||||||
|
get => 'parent-get:' . $this->stored; |
||||||
|
set { |
||||||
|
$this->stored = 'parent-set:' . $value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ChildPropertyHookCall extends ParentPropertyHookCall |
||||||
|
{ |
||||||
|
public string $value { |
||||||
|
get => parent::$value::get() . ':child-get'; |
||||||
|
set { |
||||||
|
parent::$value::set($value . ':child-set'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$point = new ChildPropertyHookCall(); |
||||||
|
$point->value = 'data'; |
||||||
|
var_dump($point->value); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(46) "parent-get:parent-set:data:child-set:child-get" |
||||||
Loading…
Reference in new issue