- 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