Protected inheritance hierarchies

php.internals

Jonathan Vollebregt

1 year ago
I came across this edge case today: https://3v4l.org/R3Q8D Both psalm and phpstan think this code is A-OK (Once you add the requisite type hints) but it causes fatal errors way back to PHP 5.0.0 I believe classes should be able to access protected properties on their siblings if the property in question was declared by a shared parent, but it seems both the engine and reflection (ie. getDeclaringClass) think that redeclaring a protected property makes it a property of the child, not the parent. This is particularly confusing since the parent class *can* access the child class' redeclared protected property, only the sibling can't. Properties were invariant until the introduction of property hooks, so the only edge cases I can think of would be in property hooks (But when the input is correctly typed this shouldn't be a problem either) Is there a technical reason for this behavior or would it be possible to relax this? - Jonathan

Jonathan Vollebregt

1 year ago
On 7/29/25 8:11 PM, Jonathan Vollebregt wrote:
> I came across this edge case today:
I was just informed that my first message ended up in someone's spam box, not sure why. I'm bumping without content in case it's happening to more people and the link tripped something.

Rob Landers

1 year ago
On Tue, Jul 29, 2025, at 20:11, Jonathan Vollebregt wrote:
> I came across this edge case today: > > https://3v4l.org/R3Q8D > > Both psalm and phpstan think this code is A-OK (Once you add the > requisite type hints) but it causes fatal errors way back to PHP 5.0.0 > > I believe classes should be able to access protected properties on their > siblings if the property in question was declared by a shared parent, > but it seems both the engine and reflection (ie. getDeclaringClass) > think that redeclaring a protected property makes it a property of the > child, not the parent. > > This is particularly confusing since the parent class *can* access the > child class' redeclared protected property, only the sibling can't. > > Properties were invariant until the introduction of property hooks, so > the only edge cases I can think of would be in property hooks (But when > the input is correctly typed this shouldn't be a problem either) > > Is there a technical reason for this behavior or would it be possible to > relax this? > > - Jonathan >
It's not an edge case, in C2, you redefine a protected variable with the same name and shadowed the original $v. That $v is different than C's $v. It's easiest to see this with static access: https://3v4l.org/0SRWb#v8.4.10 However, I don't know of any way to unshadow a property from $this to access the ancestor's value (other than using private access), but it exists and takes up memory; just accessing it is the hard part. — Rob

Jonathan Vollebregt

1 year ago
On 8/2/25 11:07 AM, Rob Landers wrote:
> It's not an edge case, in C2, you redefine a protected variable with the > same name and shadowed the original $v. That $v is different than C's > $v. It's easiest to see this with static access: > https://3v4l.org/0SRWb#v8.4.10 <https://3v4l.org/0SRWb#v8.4.10> > > However, I don't know of any way to unshadow a property from $this to > access the ancestor's value (other than using private access), but it > exists and takes up memory; just accessing it is the hard part. > > — Rob
Ah I see, thanks. Both getProperties and array casting clobber the shadowed protected properties (Unlike private properties) but you can access the property with reflection by reflecting the parent class. Could it be considered a bug that my first example produces a fatal error instead of trying to access the shadowed parent property that it has access to and is typed to use? I guess that would introduce either too much of a performance penalty recursively checking access for each protected property read, or too much complexity trying to pin down the variable type at parse time. Thanks for the info!

Alexandru Pătrănescu

1 year ago
On Sat, Aug 2, 2025 at 12:10 PM Rob Landers <rob@bottled.codes> wrote:
> On Tue, Jul 29, 2025, at 20:11, Jonathan Vollebregt wrote: > > I came across this edge case today: > > https://3v4l.org/R3Q8D > > Both psalm and phpstan think this code is A-OK (Once you add the > requisite type hints) but it causes fatal errors way back to PHP 5.0.0 > > ...<snip>... > > > It's not an edge case, in C2, you redefine a protected variable with the > same name and shadowed the original $v. That $v is different than C's $v. > It's easiest to see this with static access: > https://3v4l.org/0SRWb#v8.4.10 > > However, I don't know of any way to unshadow a property from $this to > access the ancestor's value (other than using private access), but it > exists and takes up memory; just accessing it is the hard part. > > — Rob >
Hi Rob, I'm pretty sure that there is no shadowing happening in the example. When the child instance is created, there is just one slot for the property, as the child one replaces the parent one. So basically the child property overrides the parent property rather than shadowing it. True shadowing (two slots) only occurs when the parent property is declared private. It's just that when redefining, it stores the declaring class, and so there is this sibling class access issue. I'm wondering now if the access shouldn't be relaxed, in case we have the parent class that initially defined the property. Of course, we should focus on non-static properties, as static ones are different things, and there is some shadowing there.
-- Alex

Rob Landers

1 year ago
On Sat, Aug 2, 2025, at 16:04, Alexandru Pătrănescu wrote:
> > > On Sat, Aug 2, 2025 at 12:10 PM Rob Landers <rob@bottled.codes> wrote: >> __ >> On Tue, Jul 29, 2025, at 20:11, Jonathan Vollebregt wrote: >>> I came across this edge case today: >>> >>> https://3v4l.org/R3Q8D >>> >>> Both psalm and phpstan think this code is A-OK (Once you add the >>> requisite type hints) but it causes fatal errors way back to PHP 5.0.0 >>> >>> ...<snip>... >> >> It's not an edge case, in C2, you redefine a protected variable with the same name and shadowed the original $v. That $v is different than C's $v. It's easiest to see this with static access: https://3v4l.org/0SRWb#v8.4.10 >> >> However, I don't know of any way to unshadow a property from $this to access the ancestor's value (other than using private access), but it exists and takes up memory; just accessing it is the hard part. >> >> — Rob > > > Hi Rob, > > I'm pretty sure that there is no shadowing happening in the example. > When the child instance is created, there is just one slot for the property, as the child one replaces the parent one. > So basically the child property overrides the parent property rather than shadowing it. > > True shadowing (two slots) only occurs when the parent property is declared private. > > It's just that when redefining, it stores the declaring class, and so there is this sibling class access issue. > > I'm wondering now if the access shouldn't be relaxed, in case we have the parent class that initially defined the property. > > Of course, we should focus on non-static properties, as static ones are different things, and there is some shadowing there. > > -- > Alex
Hi Alex, I’m not sure what you mean? https://3v4l.org/WKILh#v8.4.10 There is clearly shadowing going on. — Rob

Alexandru Pătrănescu

1 year ago
On Sat, Aug 2, 2025, 17:10 Rob Landers <rob@bottled.codes> wrote:
> > > On Sat, Aug 2, 2025, at 16:04, Alexandru Pătrănescu wrote: > > > > On Sat, Aug 2, 2025 at 12:10 PM Rob Landers <rob@bottled.codes> wrote: > > > On Tue, Jul 29, 2025, at 20:11, Jonathan Vollebregt wrote: > > I came across this edge case today: > > https://3v4l.org/R3Q8D > > Both psalm and phpstan think this code is A-OK (Once you add the > requisite type hints) but it causes fatal errors way back to PHP 5.0.0 > > ...<snip>... > > > It's not an edge case, in C2, you redefine a protected variable with the > same name and shadowed the original $v. That $v is different than C's $v. > It's easiest to see this with static access: > https://3v4l.org/0SRWb#v8.4.10 > > However, I don't know of any way to unshadow a property from $this to > access the ancestor's value (other than using private access), but it > exists and takes up memory; just accessing it is the hard part. > > — Rob > > > > Hi Rob, > > I'm pretty sure that there is no shadowing happening in the example. > When the child instance is created, there is just one slot for the > property, as the child one replaces the parent one. > So basically the child property overrides the parent property rather than > shadowing it. > > True shadowing (two slots) only occurs when the parent property is > declared private. > > It's just that when redefining, it stores the declaring class, and so > there is this sibling class access issue. > > I'm wondering now if the access shouldn't be relaxed, in case we have the > parent class that initially defined the property. > > Of course, we should focus on non-static properties, as static ones are > different things, and there is some shadowing there. > > -- > Alex > > > Hi Alex, > > I’m not sure what you mean? https://3v4l.org/WKILh#v8.4.10 > > There is clearly shadowing going on. > >
Hi Rob, As I said, let's leave aside the static case, as the question from Jonathan was not about that. Given the class P that defines a protected property with value 1, and a class C that extends P and re-defines the protected property with the value 2, please show me an example where you could get from an instance of class C the value 1 of the parent class property that you think it's shadowed. Bonus point, if you manage that, you could also set it to something else, and so have a hidden storage for any object of class C that is not really visible normally. As far as I know, there is no way to achieve that, and the reason is because at runtime the objects have a single slot for the protected property; the child class property overrides the parent class property when redeclared, and does not shadow it. But please prove me wrong. Thanks, Alex

Rob Landers

1 year ago
On Sat, Aug 2, 2025, at 19:04, Alexandru Pătrănescu wrote:
> > > On Sat, Aug 2, 2025, 17:10 Rob Landers <rob@bottled.codes> wrote: >> __ >> >> >> On Sat, Aug 2, 2025, at 16:04, Alexandru Pătrănescu wrote: >>> >>> >>> On Sat, Aug 2, 2025 at 12:10 PM Rob Landers <rob@bottled.codes> wrote: >>>> __ >>>> On Tue, Jul 29, 2025, at 20:11, Jonathan Vollebregt wrote: >>>>> I came across this edge case today: >>>>> >>>>> https://3v4l.org/R3Q8D >>>>> >>>>> Both psalm and phpstan think this code is A-OK (Once you add the >>>>> requisite type hints) but it causes fatal errors way back to PHP 5.0.0 >>>>> >>>>> ...<snip>... >>>> >>>> It's not an edge case, in C2, you redefine a protected variable with the same name and shadowed the original $v. That $v is different than C's $v. It's easiest to see this with static access: https://3v4l.org/0SRWb#v8.4.10 >>>> >>>> However, I don't know of any way to unshadow a property from $this to access the ancestor's value (other than using private access), but it exists and takes up memory; just accessing it is the hard part. >>>> >>>> — Rob >>> >>> >>> Hi Rob, >>> >>> I'm pretty sure that there is no shadowing happening in the example. >>> When the child instance is created, there is just one slot for the property, as the child one replaces the parent one. >>> So basically the child property overrides the parent property rather than shadowing it. >>> >>> True shadowing (two slots) only occurs when the parent property is declared private. >>> >>> It's just that when redefining, it stores the declaring class, and so there is this sibling class access issue. >>> >>> I'm wondering now if the access shouldn't be relaxed, in case we have the parent class that initially defined the property. >>> >>> Of course, we should focus on non-static properties, as static ones are different things, and there is some shadowing there. >>> >>> -- >>> Alex >> >> Hi Alex, >> >> I’m not sure what you mean? https://3v4l.org/WKILh#v8.4.10 >> >> There is clearly shadowing going on. >> >> > > Hi Rob, > > As I said, let's leave aside the static case, as the question from Jonathan was not about that. > > Given the class P that defines a protected property with value 1, > and a class C that extends P and re-defines the protected property with the value 2, > please show me an example where you could get from an instance of class C the value 1 of the parent class property that you think it's shadowed. > Bonus point, if you manage that, you could also set it to something else, and so have a hidden storage for any object of class C that is not really visible normally. > > As far as I know, there is no way to achieve that, and the reason is because at runtime the objects have a single slot for the protected property; the child class property overrides the parent class property when redeclared, and does not shadow it. > But please prove me wrong. > > > Thanks, > Alex >
I mentioned in my first reply, there is no way to get an instance-level property unshadowed. It is there though (according to inheritance.c, if I’m reading it right, it is still accessible, just not from user-land). In any case, there are lots of interesting footguns with properties and inheritance: Problem with abstract nested object · Issue #47 · Crell/Serde <https://github.com/Crell/Serde/issues/47#issuecomment-1890966829>.
> Could it be considered a bug that my first example produces a fatal > error instead of trying to access the shadowed parent property that it > has access to and is typed to use?
Other languages (such as C#, Java, etc: https://www.programiz.com/online-compiler/0ud6UO24mHOTU) don’t allow you to access protected properties/methods on sibling classes. This is because "protected" is usually used in the context of inheritance; access is usually restricted to "myself" or "children" and a sibling is neither of those. If there is a bug, the bug is that you can access a sibling’s protected properties, at all. — Rob

Удальцов Валентин

1 year ago
On Sat, Aug 2, 2025, at 22:17, Rob Landers <rob@bottled.codes> wrote:
> On Sat, Aug 2, 2025, at 19:04, Alexandru Pătrănescu wrote: > > > > On Sat, Aug 2, 2025, 17:10 Rob Landers <rob@bottled.codes> wrote: > > > > > On Sat, Aug 2, 2025, at 16:04, Alexandru Pătrănescu wrote: > > > > On Sat, Aug 2, 2025 at 12:10 PM Rob Landers <rob@bottled.codes> wrote: > > > On Tue, Jul 29, 2025, at 20:11, Jonathan Vollebregt wrote: > > I came across this edge case today: > > https://3v4l.org/R3Q8D > > Both psalm and phpstan think this code is A-OK (Once you add the > requisite type hints) but it causes fatal errors way back to PHP 5.0.0 > > ...<snip>... > > > It's not an edge case, in C2, you redefine a protected variable with the > same name and shadowed the original $v. That $v is different than C's $v. > It's easiest to see this with static access: > https://3v4l.org/0SRWb#v8.4.10 > > However, I don't know of any way to unshadow a property from $this to > access the ancestor's value (other than using private access), but it > exists and takes up memory; just accessing it is the hard part. > > — Rob > > > > Hi Rob, > > I'm pretty sure that there is no shadowing happening in the example. > When the child instance is created, there is just one slot for the > property, as the child one replaces the parent one. > So basically the child property overrides the parent property rather than > shadowing it. > > True shadowing (two slots) only occurs when the parent property is > declared private. > > It's just that when redefining, it stores the declaring class, and so > there is this sibling class access issue. > > I'm wondering now if the access shouldn't be relaxed, in case we have the > parent class that initially defined the property. > > Of course, we should focus on non-static properties, as static ones are > different things, and there is some shadowing there. > > -- > Alex > > > Hi Alex, > > I’m not sure what you mean? https://3v4l.org/WKILh#v8.4.10 > > There is clearly shadowing going on. > > > > Hi Rob, > > As I said, let's leave aside the static case, as the question from > Jonathan was not about that. > > Given the class P that defines a protected property with value 1, > and a class C that extends P and re-defines the protected property with > the value 2, > please show me an example where you could get from an instance of class C > the value 1 of the parent class property that you think it's shadowed. > Bonus point, if you manage that, you could also set it to something else, > and so have a hidden storage for any object of class C that is not really > visible normally. > > As far as I know, there is no way to achieve that, and the reason is > because at runtime the objects have a single slot for the protected > property; the child class property overrides the parent class property when > redeclared, and does not shadow it. > But please prove me wrong. > > > Thanks, > Alex > > > I mentioned in my first reply, there is no way to get an instance-level > property unshadowed. It is there though (according to inheritance.c, if I’m > reading it right, it is still accessible, just not from user-land). > > In any case, there are lots of interesting footguns with properties and > inheritance: Problem with abstract nested object · Issue #47 · Crell/Serde > <https://github.com/Crell/Serde/issues/47#issuecomment-1890966829>. > > Could it be considered a bug that my first example produces a fatal > error instead of trying to access the shadowed parent property that it > has access to and is typed to use? > > > Other languages (such as C#, Java, etc: > https://www.programiz.com/online-compiler/0ud6UO24mHOTU) don’t allow you > to access protected properties/methods on sibling classes. This is because > "protected" is usually used in the context of inheritance; access is > usually restricted to "myself" or "children" and a sibling is neither of > those. If there is a bug, the bug is that you can access a sibling’s > protected properties, at all. > > — Rob >
> If there is a bug, the bug is that you can access a sibling’s protected
properties, at all. In 2006 the absence of this feature was fixed as a bug and meged in PHP 5.2: https://bugs.php.net/bug.php?id=37632 In 2020 Nikita Popov agreed that this is expected: https://x.com/nikita_ppv/status/1261633126687805440 So one way is to explicitly mention this feature in the Visibility docs <https://www.php.net/manual/en/language.oop5.visibility.php> and fix the redeclaration issue to make things consistent. The other way is to deprecate sibling relations.
-- Valentin

Rob Landers

1 year ago
On Sat, Aug 2, 2025, at 22:18, Valentin Udaltsov wrote:
> On Sat, Aug 2, 2025, at 22:17, Rob Landers <rob@bottled.codes> wrote: >> __ >> On Sat, Aug 2, 2025, at 19:04, Alexandru Pătrănescu wrote: >>> >>> >>> On Sat, Aug 2, 2025, 17:10 Rob Landers <rob@bottled.codes> wrote: >>>> __ >>>> >>>> >>>> On Sat, Aug 2, 2025, at 16:04, Alexandru Pătrănescu wrote: >>>>> >>>>> >>>>> On Sat, Aug 2, 2025 at 12:10 PM Rob Landers <rob@bottled.codes> wrote: >>>>>> __ >>>>>> On Tue, Jul 29, 2025, at 20:11, Jonathan Vollebregt wrote: >>>>>>> I came across this edge case today: >>>>>>> >>>>>>> https://3v4l.org/R3Q8D >>>>>>> >>>>>>> Both psalm and phpstan think this code is A-OK (Once you add the >>>>>>> requisite type hints) but it causes fatal errors way back to PHP 5.0.0 >>>>>>> >>>>>>> ...<snip>... >>>>>> >>>>>> It's not an edge case, in C2, you redefine a protected variable with the same name and shadowed the original $v. That $v is different than C's $v. It's easiest to see this with static access: https://3v4l.org/0SRWb#v8.4.10 >>>>>> >>>>>> However, I don't know of any way to unshadow a property from $this to access the ancestor's value (other than using private access), but it exists and takes up memory; just accessing it is the hard part. >>>>>> >>>>>> — Rob >>>>> >>>>> >>>>> Hi Rob, >>>>> >>>>> I'm pretty sure that there is no shadowing happening in the example. >>>>> When the child instance is created, there is just one slot for the property, as the child one replaces the parent one. >>>>> So basically the child property overrides the parent property rather than shadowing it. >>>>> >>>>> True shadowing (two slots) only occurs when the parent property is declared private. >>>>> >>>>> It's just that when redefining, it stores the declaring class, and so there is this sibling class access issue. >>>>> >>>>> I'm wondering now if the access shouldn't be relaxed, in case we have the parent class that initially defined the property. >>>>> >>>>> Of course, we should focus on non-static properties, as static ones are different things, and there is some shadowing there. >>>>> >>>>> -- >>>>> Alex >>>> >>>> Hi Alex, >>>> >>>> I’m not sure what you mean? https://3v4l.org/WKILh#v8.4.10 >>>> >>>> There is clearly shadowing going on. >>>> >>>> >>> >>> Hi Rob, >>> >>> As I said, let's leave aside the static case, as the question from Jonathan was not about that. >>> >>> Given the class P that defines a protected property with value 1, >>> and a class C that extends P and re-defines the protected property with the value 2, >>> please show me an example where you could get from an instance of class C the value 1 of the parent class property that you think it's shadowed. >>> Bonus point, if you manage that, you could also set it to something else, and so have a hidden storage for any object of class C that is not really visible normally. >>> >>> As far as I know, there is no way to achieve that, and the reason is because at runtime the objects have a single slot for the protected property; the child class property overrides the parent class property when redeclared, and does not shadow it. >>> But please prove me wrong. >>> >>> >>> Thanks, >>> Alex >>> >> >> I mentioned in my first reply, there is no way to get an instance-level property unshadowed. It is there though (according to inheritance.c, if I’m reading it right, it is still accessible, just not from user-land). >> >> In any case, there are lots of interesting footguns with properties and inheritance: Problem with abstract nested object · Issue #47 · Crell/Serde <https://github.com/Crell/Serde/issues/47#issuecomment-1890966829>. >> >>> Could it be considered a bug that my first example produces a fatal >>> error instead of trying to access the shadowed parent property that it >>> has access to and is typed to use? >> >> Other languages (such as C#, Java, etc: https://www.programiz.com/online-compiler/0ud6UO24mHOTU) don’t allow you to access protected properties/methods on sibling classes. This is because "protected" is usually used in the context of inheritance; access is usually restricted to "myself" or "children" and a sibling is neither of those. If there is a bug, the bug is that you can access a sibling’s protected properties, at all. >> >> — Rob > > > If there is a bug, the bug is that you can access a sibling’s protected properties, at all. > > In 2006 the absence of this feature was fixed as a bug and meged in PHP 5.2: https://bugs.php.net/bug.php?id=37632 > In 2020 Nikita Popov agreed that this is expected: https://x.com/nikita_ppv/status/1261633126687805440 > > So one way is to explicitly mention this feature in the Visibility docs <https://www.php.net/manual/en/language.oop5.visibility.php> and fix the redeclaration issue to make things consistent. > > The other way is to deprecate sibling relations. > > -- > Valentin
I don’t think the redeclaration is a bug though, as is mentioned in the linked bug report about properties: https://bugs.php.net/bug.php?id=37212, it is pretty clear to me that people expect a redeclaration would be a fatal error, but not accessing a property declared in a shared parent scope (emphasis mine):
> The property *is not being redeclared in C*, though. *It is still a property of A, structure-wise.* A method declared and called in the same way as the property does not cause any error.
This has been the case for years, so I don’t think it is a bug. I was only saying that if there is a bug, the bug would be that you can access another class’s protected properties that aren’t a parent or sibling. I didn’t really go into why, but IMHO, it breaks LSP, since it allows sibling classes to depend on each other’s internals, breaking substitutability (particularly in regards to hooks). — Rob

Rowan Tommins [IMSoP]

1 year ago
On 2 August 2025 20:12:59 BST, Rob Landers <rob@bottled.codes> wrote:
>I mentioned in my first reply, there is no way to get an instance-level property unshadowed. It is there though (according to inheritance.c, if I’m reading it right, it is still accessible, just not from user-land).
I'd be interested to see what code you're looking at. As I showed in my last email, there's an explicit difference in how private and protected property names are mangled, and it's consistent with how reflection, serialisation, and debug functions output them - which is that there is only one property, no matter how many times in the inheritance chain it is redefined. Rowan Tommins [IMSoP]

Rob Landers

1 year ago
On Sat, Aug 2, 2025, at 22:33, Rowan Tommins [IMSoP] wrote:
> On 2 August 2025 20:12:59 BST, Rob Landers <rob@bottled.codes> wrote: > >I mentioned in my first reply, there is no way to get an instance-level property unshadowed. It is there though (according to inheritance.c, if I’m reading it right, it is still accessible, just not from user-land). > > > I'd be interested to see what code you're looking at. As I showed in my last email, there's an explicit difference in how private and protected property names are mangled, and it's consistent with how reflection, serialisation, and debug functions output them - which is that there is only one property, no matter how many times in the inheritance chain it is redefined. > > > Rowan Tommins > [IMSoP] >
If this were the case, then creating a base class with default values wouldn’t be possible. The memory exists and is set aside for that. The child class shadows this value, but the original value still exists. You can get to it by calling into the parent class entry and accessing it that way. There is no way to get to that value from an instance in php, though — from the perspective of the child, shadowing loses the original default value. — Rob

Rowan Tommins [IMSoP]

1 year ago
On 2 August 2025 21:59:20 BST, Rob Landers <rob@bottled.codes> wrote:
>If this were the case, then creating a base class with default values wouldn’t be possible. The memory exists and is set aside for that.
Sure it would: the default value is just an assignment that happens at a particular point of the object's lifecycle. For a child class which overrides the default of a parent (on a public or protected property), only the child class's assignment will ever be visible. So it would be perfectly valid for the class entry for the child class to only store that one assignment. I don't know if that actually happens; maybe the cost of de-duplicating is not seen as worthwhile, and the assignments are just run in sequence every time. Regardless, the philosophical question in this thread seems to be whether re-declaring a protected property should change the "ownership" of that property. I think it's natural that a protected property *only* declared in a sibling class can't be accessed, so some ownership needs to be tracked. What seems surprising is that the ownership changes if the same property is re-declared, especially since the new declaration has to match the original (e.g. you can't change the type), and every possible access tells the user the two declarations have been completely merged. Intuitively, an identical declaration with no change other than a default value looks like it would be the same as overwriting the default in a constructor, but that is not the case. (https://3v4l.org/5iIak vs https://3v4l.org/rL8pX) I'm inclined to agree that this is a bug, regardless of whether it's difficult to fix in the implementation. Rowan Tommins [IMSoP]

Rob Landers

1 year ago
On Sun, Aug 3, 2025, at 11:10, Rowan Tommins [IMSoP] wrote:
> On 2 August 2025 21:59:20 BST, Rob Landers <rob@bottled.codes> wrote: > >If this were the case, then creating a base class with default values wouldn’t be possible. The memory exists and is set aside for that. > > Sure it would: the default value is just an assignment that happens at a particular point of the object's lifecycle. For a child class which overrides the default of a parent (on a public or protected property), only the child class's assignment will ever be visible. So it would be perfectly valid for the class entry for the child class to only store that one assignment. I don't know if that actually happens; maybe the cost of de-duplicating is not seen as worthwhile, and the assignments are just run in sequence every time. > > Regardless, the philosophical question in this thread seems to be whether re-declaring a protected property should change the "ownership" of that property. I think it's natural that a protected property *only* declared in a sibling class can't be accessed, so some ownership needs to be tracked. > > What seems surprising is that the ownership changes if the same property is re-declared, especially since the new declaration has to match the original (e.g. you can't change the type), and every possible access tells the user the two declarations have been completely merged. > > Intuitively, an identical declaration with no change other than a default value looks like it would be the same as overwriting the default in a constructor, but that is not the case. (https://3v4l.org/5iIak vs https://3v4l.org/rL8pX) > > I'm inclined to agree that this is a bug, regardless of whether it's difficult to fix in the implementation. > > > Rowan Tommins > [IMSoP] >
I'm not sure that this is a bug. You can redeclare the same type and add hooks (or change them), which breaks all assumptions about substitutability. class A { protected int $v = 2; } class B extends A { public function getValue(A $v): void { echo $v->v; } } class C extends A { protected int $v { set => $this->v * 2; } } $b = new B; $c = new C; $b->getValue($b); $b->getValue($c); C changes all assumptions from B's point of view (technically C is a violation of LSP from the perspective of A, thus it should not pretend to be substitutable as A from the perspective of B when used in this way -- though other properties/methods may in fact be substitutable and be useful). — Rob

Rowan Tommins [IMSoP]

1 year ago
On 02/08/2025 15:09, Rob Landers wrote:
> I’m not sure what you mean? https://3v4l.org/WKILh#v8.4.10 > > There is clearly shadowing going on.
There's a lot of confusing code in that example, so I'm not really sure what it's illustrating. This example seems more to the point: https://3v4l.org/FVhXa Regardless of whether you look up the reflection property on the parent or the child, it points to the same slot for property 'v' In contrast, the same reflection code for a private property finds both values stored in the object: https://3v4l.org/S7GmM You can see the same result with var_dump (and serialize, and debug_zval_dump, ...): https://3v4l.org/FqecC I think the relevant code is this, in zend_declare_typed_property: https://heap.space/xref/php-src/Zend/zend_API.c?r=78d96e94fa8e05dd59d03aa4891fa843ebc93ef8#4661 if (access_type & ZEND_ACC_PUBLIC) {     property_info->name = zend_string_copy(name); } else if (access_type & ZEND_ACC_PRIVATE) {     property_info->name = zend_mangle_property_name(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), ZSTR_VAL(name), ZSTR_LEN(name), is_persistent_class(ce)); } else {     ZEND_ASSERT(access_type & ZEND_ACC_PROTECTED);     property_info->name = zend_mangle_property_name("*", 1, ZSTR_VAL(name), ZSTR_LEN(name), is_persistent_class(ce)); } For a private name, the mangled name includes the class where it's defined, which is where the shadowing comes from: the final merged symbol table has two differently named slots. But for a protected name, the prefix is just "*", so it's the same slot no matter how many times it appears in the hierarchy, just like for public properties (whose names aren't mangled at all). How this relates back to the original example in the thread, I'm not sure, but it's definitely not "shadowing" in the same sense as a private property.
-- Rowan Tommins [IMSoP]

Jonathan Vollebregt

1 year ago
On 8/2/25 7:19 PM, Rowan Tommins [IMSoP] wrote:
> This example seems more to the point:
I didn't notice the static in Rob's example. Alex/Rowan are right, instance properties are definitely not shadowed. Anyway that brings back my original question: Can we fix/relax this? I don't see any need to prevent accessing properties that were declared on a parent. Should I just submit a bug report on github?