Welcome! Log In Create A New Profile

Advanced

[PHP-DEV] Re: Ambiguity in the oop5 visibility docs

Posted by Christoph M. Becker 
Christoph M. Becker
[PHP-DEV] Re: Ambiguity in the oop5 visibility docs
May 25, 2018 02:40PM
On 17.05.2018 at 12:12, Andrey O Gromov wrote:

> Please take a look on patch "EN error fixed" from user "rjhdby"
>
>
> <emphasis>protected</emphasis> or
> <emphasis>private</emphasis>. Class members declared public can be
> accessed everywhere. Members declared protected can be accessed
> - only within the class itself and by inheriting and parent
> - classes. Members declared as private may only be accessed by the
> + only within the class itself and by inheriting.
> + Members declared as private may only be accessed by the
> class that defines the member.
> </para>
> -
> + <note>You can access protected members of a class or object from
> inherited methods.
> + Do <emphasis>NOT</emphasis> rely on this behavior, because it can be
> changed at any time.</note>
> <sect2 xml:id="language.oop5.visibility-members">
> <title>Property Visibility</title>
> <para>

I think we should discuss this on the [email protected] mailing list. Perhaps
the current behavior is by design, and will “never” change.

--
Christoph M. Becker


--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
On 25 May 2018 13:29:41 BST, "Christoph M. Becker" <[email protected]>
wrote:
>
> On 17.05.2018 at 12:12, Andrey O Gromov wrote:
>
> Please take a look on patch "EN error fixed" from user "rjhdby"
>>
>>
>> <emphasis>protected</emphasis> or
>> <emphasis>private</emphasis>. Class members declared public can be
>> accessed everywhere. Members declared protected can be accessed
>> - only within the class itself and by inheriting and parent
>> - classes. Members declared as private may only be accessed by the
>> + only within the class itself and by inheriting.
>> + Members declared as private may only be accessed by the
>> class that defines the member.
>> </para>
>> -
>> + <note>You can access protected members of a class or object from
>> inherited methods.
>> + Do <emphasis>NOT</emphasis> rely on this behavior, because it can be
>> changed at any time.</note>
>> <sect2 xml:id="language.oop5.visibility-members">
>> <title>Property Visibility</title>
>> <para>
>>
>
> I think we should discuss this on the [email protected] mailing list. Perhaps
> the current behavior is by design, and will “never” change.
>
>
I'm not sure what exactly is "ambiguous" or "might change" here. The
current text accurately describes what "protected" means in PHP, and in
many other languages: a property which may be accessed only within the
current class, classes inheriting from it, and classes from which it
inherits.

It may seem odd at first that a parent class can access protected members
of a child class, but this is actually a necessary consequence of the child
class being able to *override* that member. Consider this pattern:

class A {
public function something() {
// important logic
$this->onSomethingHook($someEventData);
// more important logic
}
protected function onSomethingHook($someEventData) {
// No logic; extension point provided for sub-classes to add custom
behaviour
// Could even be declared abstract if this is an abstract base
class and a required hook
}
}

class B extends A {
protected function onSomethingHook($someEventData) {
// Custom logic here, which will be called from class A
}
}

This relies on code in class A being able to call the protected method in
class B.

Given that PHP is largely "duck typed", it would also be possible for class
A to never declare the onSomethingHook method, and call it anyway (maybe
checking with method_exists first): https://3v4l.org/hOEuO

If this isn't the aspect of the current wording that was confusing, please
can you clarify, as the proposed edit seems to me to be just plain wrong.

Regards,
--
Rowan Collins
[IMSoP]
On Fri, May 25, 2018 at 6:47 PM, Rowan Collins <[email protected]>
wrote:
>
> I'm not sure what exactly is "ambiguous" or "might change" here. The
> current text accurately describes what "protected" means in PHP, and in
> many other languages: a property which may be accessed only within the
> current class, classes inheriting from it, and classes from which it
> inherits.


I think that what the note may be referring to is the fact that currently,
a child class can access the protected members of another child class if
this member was declared on the parent class.
However, this is a bug as described on https://bugs.php.net/bug.php?id=50892
which hasn't been fixed yet as the potential BC break makes it more
suitable for a major version.

Regards,
Pedro
That's not really a bug, that's expected behaviour on which stuff like
friend classes and entire libraries are based upon...

On Fri, 25 May 2018, 20:50 Pedro Magalhães, <[email protected]> wrote:

> On Fri, May 25, 2018 at 6:47 PM, Rowan Collins <[email protected]>
> wrote:
> >
> > I'm not sure what exactly is "ambiguous" or "might change" here. The
> > current text accurately describes what "protected" means in PHP, and in
> > many other languages: a property which may be accessed only within the
> > current class, classes inheriting from it, and classes from which it
> > inherits.
>
>
> I think that what the note may be referring to is the fact that currently,
> a child class can access the protected members of another child class if
> this member was declared on the parent class.
> However, this is a bug as described on
> https://bugs.php.net/bug.php?id=50892
> which hasn't been fixed yet as the potential BC break makes it more
> suitable for a major version.
>
> Regards,
> Pedro
>
On 25/05/2018 19:48, Pedro Magalhães wrote:
> On Fri, May 25, 2018 at 6:47 PM, Rowan Collins
> <[email protected] <mailto:[email protected]>> wrote:
>
> I'm not sure what exactly is "ambiguous" or "might change" here. The
> current text accurately describes what "protected" means in PHP,
> and in
> many other languages: a property which may be accessed only within the
> current class, classes inheriting from it, and classes from which it
> inherits.
>
>
> I think that what the note may be referring to is the fact that
> currently, a child class can access the protected members of another
> child class if this member was declared on the parent class.
> However, this is a bug as described on
> https://bugs.php.net/bug.php?id=50892 which hasn't been fixed yet as
> the potential BC break makes it more suitable for a major version.

Again, this is perfectly reasonable if you consider polymorphism:

- Access is class-based, not instance-based: you can call private
members of a different instance of the same class.
- For the same reason, you can access protected members on an instance
of a parent class.
- If you ask for an instance of class A, you may at run-time actually
receive an instance of some sub-class of A.
- So, if classes B1 and B2 both inherit from A, a method on class B1
might ask for an instance of A, and be given an instance of B2; when it
calls a method on that instance, it's the definition in B2 which will be
accessed.

Here's a concrete example (copied below): https://3v4l.org/LeClo

Note that this example also works in Java:
https://tio.run/##vZMxb9swE[email protected][email protected]/[email protected]Gpg2h34a7vsD/7nBhGFVfE33H7s7mVT8eVz6p75yvkeLPT9gBmM/iCxgTAnn9FXDoF7gm2pZYlbciDLLVR3Rx0aMFPJp649haWyKXwrrYqb/sa8E1HjXWYG6cVoFJ5mq7jkvy/IGyNLg36w106Exfmt8/[email protected]@[email protected]/[email protected]/[email protected]/Il/[email protected]/A1MlXA1i3UswsbmKC48nGvi4qo4fjHAX8nTR0AL2sa2bBPzp2FwttoYQCkpvA16VcRsR3mfPLqiaN5M8W78ZvCHwys


abstract class Price {
    protected $value;

    public function __construct($value) {
        $this->value = $value;
    }

    // Duck typing means this declaration is actually optional
    abstract protected function getRawValue();

    public function getFormattedValue() {
        // Calls a protected method of whichever child class is $this
        return number_format($this->getRawValue(), 2);
    }

    public function add(Price $other) {
        // Calls a protected method of whichever child class is $other
        $this->value += $other->getRawValue();
    }
}

class TaxFreePrice extends Price {
    protected function getRawValue() {
        return $this->value;
    }
}

class PriceWithTax extends Price{
    const TAX_RATE=1.2;

    protected function getRawValue() {
        return $this->value * self::TAX_RATE;
    }
}

$a = new TaxFreePrice(10);
$b = new PriceWithTax(10);
echo $a->getFormattedValue(), PHP_EOL;
echo $b->getFormattedValue(), PHP_EOL;

// Here, an instance of TaxFreePrice will access a protected member of
PriceWithTax
$a->add($b);
echo $a->getFormattedValue(), PHP_EOL;


Regards,

--
Rowan Collins
[IMSoP]
On 26/05/2018 15:04, Rowan Collins wrote:
> Here's a concrete example...


Sorry, slight mistake in my example: to be equivalent to the case
discussed, the Subclass::add(ParentClass $other) method should be
declared on one of the child classes. But it still works in both languages:

https://3v4l.org/IInDq

https://tio.run/##vVPBThsx[email protected][email protected]LdObOsfMJ4bhcKuDGKtwkTO87njeNOx1XBDf4vrH8Kqe73feoa6c75DjywmbCTg9hUs0JgBm/[email protected]f[email protected]T/[email protected]/NbfRrdLMl700bmH/00v4BOfiayZ7NilK8m9Q2/i8moqBkbdmd/GqvmOv7eLXb0C/[email protected][email protected]hfYi6JuYmwnPlkRXFkMfmw/KHNWw2fwE


Regards,

--
Rowan Collins
[IMSoP]


--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
On 25.05.2018 at 19:47, Rowan Collins wrote:

> On 25 May 2018 13:29:41 BST, "Christoph M. Becker" <[email protected]>
> wrote:
>>
>> I think we should discuss this on the [email protected] mailing list. Perhaps
>> the current behavior is by design, and will “never” change.
>
> Given that PHP is largely "duck typed", it would also be possible for class
> A to never declare the onSomethingHook method, and call it anyway (maybe
> checking with method_exists first): https://3v4l.org/hOEuO

Thanks, Rowan! I had not considered duck typing, but when doing so, it
is pretty clear that we do not want to change the current behavior, and
as such the patch should be rejected.

--
Christoph M. Becker

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
On Sat, May 26, 2018 at 3:04 PM, Rowan Collins <[email protected]>
wrote:
>
> Note that this example also works in Java: https://tio.run/##vZMxb9swEIVn
> 61fcKKctbWctMhUIuhgomqIdigwn8mIxpUiDPNkpDP92hxJlW4rsIUBRQhP5
> @[email protected]/[email protected]
> LAnirpbpTt7XTDuHZnGpg2h34a7vsD/7nBhGFVfE33H7s7mVT8eVz6p75yvk
> eLPT9gBmM/iCxgTAnn9FXDoF7gm2pZYlbciDLLVR3Rx0aMFPJp649haWyKXw
> rrYqb/sa8E1HjXWYG6cVoFJ5mq7jkvy/IGyNLg36w106Exfmt8/2WZYsfuDL
> [email protected]@lLudoQNVz6IZ8bmRE3CL80lxGpCHx
> [email protected]/[email protected]/sNd29fsR0K9CU2gy
> [email protected]/Il/[email protected]/A1MlXA1i3UswsbmKC48nGvi
> 4qo4fjHAX8nTR0AL2sa2bBPzp2FwttoYQCkpvA16VcRsR3mfPLqiaN5M8W78ZvCHwys
>

Java's behavior stems from the fact that protected offers package-level
visibility. A concept that we don't have in PHP. To offer a counter
example, C# disallows it: https://ideone.com/DkiCeM

PHP started allowing this in 5.2 due to a bug report stating that it should
be possible: https://bugs.php.net/bug.php?id=37212
On 26/05/2018 16:39, Pedro Magalhães wrote:
> On Sat, May 26, 2018 at 3:04 PM, Rowan Collins
> <[email protected] <mailto:[email protected]>> wrote:
>
> Note that this example also works in Java:
> https://tio.run/##vZMxb9swE[email protected][email protected]/[email protected]Gpg2h34a7vsD/7nBhGFVfE33H7s7mVT8eVz6p75yvkeLPT9gBmM/iCxgTAnn9FXDoF7gm2pZYlbciDLLVR3Rx0aMFPJp649haWyKXwrrYqb/sa8E1HjXWYG6cVoFJ5mq7jkvy/IGyNLg36w106Exfmt8/[email protected]@[email protected]/[email protected]/[email protected]/Il/[email protected]/A1MlXA1i3UswsbmKC48nGvi4qo4fjHAX8nTR0AL2sa2bBPzp2FwttoYQCkpvA16VcRsR3mfPLqiaN5M8W78ZvCHwys
> <https://tio.run/#%23vZMxb9swE[email protected][email protected]/[email protected]Gpg2h34a7vsD/7nBhGFVfE33H7s7mVT8eVz6p75yvkeLPT9gBmM/iCxgTAnn9FXDoF7gm2pZYlbciDLLVR3Rx0aMFPJp649haWyKXwrrYqb/sa8E1HjXWYG6cVoFJ5mq7jkvy/IGyNLg36w106Exfmt8/[email protected]@[email protected]/[email protected]/[email protected]/Il/[email protected]/A1MlXA1i3UswsbmKC48nGvi4qo4fjHAX8nTR0AL2sa2bBPzp2FwttoYQCkpvA16VcRsR3mfPLqiaN5M8W78ZvCHwys>;
>
>
> Java's behavior stems from the fact that protected offers
> package-level visibility. A concept that we don't have in PHP. To
> offer a counter example, C# disallows it: https://ideone.com/DkiCeM

I stand corrected on the Java front; I had incorrectly assumed
"protected" had the same meaning there. The error message given in that
C# example is interesting:

> Cannot access protected member `foo._protected' via a qualifier of
type `foo'. The qualifier must be of type `kid' or derived from it

The compiler is able to complain in advance that the access would not be
type-safe. You can actually substitute "foo b = new kid();" and get the
same error, because it's the static type of the variable that is the
problem, not the dynamic type of the instance. PHP could never detect
this at compile-time, and doesn't guarantee the safety of *any* method
call, so it would have to fail at run-time.


> PHP started allowing this in 5.2 due to a bug report stating that it
> should be possible: https://bugs.php.net/bug.php?id=37212

As I look at more examples of this, I am finding there are
inconsistencies across two dimensions: methods vs properties, and
inheriting the member directly vs overriding it. See
https://3v4l.org/NSCKW and https://3v4l.org/otbY1 for various versions.

In PHP 5.1, a protected *method* could be called on any instance with a
shared parent, as long as it was declared on the parent class, and
wasn't re-declared in the sub-class. This is weird, because it's a
reasonable assumption that if you can call a method on an instance of A,
you ought to be able to call it on any sub-class of A. With this
original behaviour, a sub-class can break that assumption simply by
declaring an over-ride to a protected method.

In PHP 5.2, this inconsistency was removed, and the sub-class can call a
protected method of its parent even if given a sub-class that over-rides
that method.

Properties, meanwhile, are a step behind: in PHP 5.1, a protected
property couldn't be accessed on an instance with a shared parent at
all, but since 5.2, it's had the inconsistent access previously enjoyed
by methods: you can access it as long as it hasn't been re-declared in
the child class. Again, this inconsistency doesn't make much sense, and
breaks substitutability.


From the error message, the C# solution appears to be to ban access not
just on parallel sub-classes, but on instances of the parent class
itself. Translated to the run-time typing of PHP, this would also need
to throw an error:

class A {
    protected $x = 1;
}
class B extends A {
    public function foo($a) {
        echo $a->x;
    }
}
$a = new A;
$b = new B;
$b->foo($a);


That would at least be a consistent alternative to the current
behaviour, although I'm not entirely sure if there are any concrete
benefits of changing. Both behaviours are quite hard to explain, and may
be surprising to some users.

Regards,

--
Rowan Collins
[IMSoP]
Sorry, only registered users may post in this forum.

Click here to login