Re: Protected inheritance hierarchies
| From: | Rob Landers | Date: | Sat, 02 Aug 2025 21:18:33 +0000 |
| Subject: | Re: Protected inheritance hierarchies | ||
| References: | 1 2 3 4 5 6 7 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-128378@lists.php.net to get a copy of this message | ||
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