14
votes

I've found something that appears to be a strange inheritance issue in PHP.

From the PHP manual:

Members declared protected can be accessed only within the class itself and by inherited and parent classes.

To me this means: A can access the protected members of B if A instanceof B or B instanceof A.

However, if both A and B extend Foo, and Foo has a protected constructor which is not overwritten in B, then I can create an instance of B from within A. This does not make sense to me, because A is not an instance of B and B is not an instance of A. I can also call the protected method $b->test() from within A, which executes the method implemented in B. (If B does not redeclare test() then the implementation in Foo is executed.) To me this is even more strange because I cannot create an instance of B from within A if B directly implements a protected constructor. It seems strange that I cannot access a protected constructor (also declared in the parent class) but accessing a protected method (also declared in the parent class) is no problem.

Note that I do get the expected behavior when I use a class C which does not extend Foo. If I try to instantiate B from within C, I get a fatal error because I'm trying to access a protected constructor. If I add a public constructor to B it is possible to instantiate it (which is expected) and I still cannot access the protected method test() (this is also expected behavior). I expect the same behavior when using A instead of C.

Sample code which explains again:

class Foo {
    protected function __construct() {
        echo('Constructing ' . get_called_class());
    }

    protected function test() {
        echo('Hello world ' . __METHOD__);
    }
}

class A extends Foo {
    public function __construct() {
        parent::__construct();
    }

    public function testB() {
        // Both of these lines work
        $b = new B();
        $b->test();
    }
}

class B extends Foo {
    protected function test() {
        echo('Hello world Again ' . __METHOD__);
    }
}

class C {
    public function __construct() {
    }

    public function testB() {
        // Both of these lines cause fatal errors
        $b = new B();
        $b->test();
    }
}

$a = new A();
$a->testB();

$c = new C();
$c->testB();

I'm probably not seeing something, but I can't find what. Could anyone explain the behavior to me?

2
Very strange behaviour indeed.Sherlock
Do you want an explanation of the rationale or of the implementation behind this behavior? Since this is PHP, there's a high chance that there is no rationale.Jon
If there is a rationale, I would like to know what it is.Arjan
if you add a protected constructor in B the A::testB does not works. So I think that when A::testB try to call B constructor, it try to call the Foo constructor directly (because it missed in B). The Foo::_constructor is accessible from A, so it works (maybe it has no memory of child class). I don't tell here if it's right or wrong… but I think that happens thisLuca Rainone

2 Answers

6
votes

You can access those methods because there is a declaration of them as protected in Foo, which is your parent and that gives you permission to access it. If you remove the declaration from the parent and declare the protected method in B you will get a Fatal Error.

This is reported as a bug in PHP https://bugs.php.net/bug.php?id=50892

1
votes

There is no rationale about this, it has been reported 2 years ago: https://bugs.php.net/bug.php?id=52120