Strange behavior in PHP with method visibility


Normally I feel very comfortable with PHP, but not all is good. There’s some things I don’t like. One is the lack of real annotations and another one is this rare behaviour with visibility within the OO. Let me explain this a little bit.

Last week I was refactoring one old script. I removed a coupling problem with DI. Something like this:

class AnotherClass
{
    protected function foo()
    {
        return "bar";
    }
}

class OneClass extends AnotherClass{
    private $object;

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

    public function myFunction()
    {
        return $this->object->foo();
    }
}

$anotherClass = new AnotherClass();
$oneClass = new OneClass($anotherClass);

echo $oneClass->myFunction();

It works, but I realized that I didn’t need to extend OneClass with AnotherClass (due to the DI), so I removed it. Then the script crashed:
Fatal error: Call to protected method AnotherClass::foo() from context ‘OneClass’

Obviously it was due to the protected function AnotherClass::foo. But, Why it worked when I extends OneClass with AnotherClass? The visibility is the same.

I reported this “bug” to the PHP community. PHP community is great. I had an answer very quick. It was not a bug. I needed to read several times the answer to understand it but finally did it.

As someone answer me:

foo() is protected and was defined in the context of OneClass. The access is done in the context of AnotherClass. AnotherClass is a subclass of OneClass (the context where foo() was defined). Therefore access is granted

In PHP the visibility belongs to the class not to the instance of the class. I understand the reason, but my mind compute it as a bug 😦 and it isn’t. It’s a feature.

What do you think?

Advertisement

17 thoughts on “Strange behavior in PHP with method visibility

  1. Hey Gonzalo!

    I’ve been working a lot with DIC and obviously with IOC in PHP world lately (hands on ZF2), but I don’t really understand the reason why you’d want to access a protected method of an injected object. To me, that feels wrong. At least for me, I try to access only the public API of injected objects.

    I think you are looking for something like Java’s “package” access level, no? Because otherwise, I see no use case for the code you just wrote…

    Cheers!

    1. I’m agree with you. I don’t need this “protected” function. It must be public. But when I was refactoring one old code I saw this script (OK not exactly this, it’s an example). This post is about: why this script works? Are we accessing to one protected function as public?

      Maybe (as you correctly comment) the example is not one good pattern, but it’s not about patterns is about visibility.

  2. I would agree that PHP is wrong in allowing $this->object->foo(); to be called. The visibility scope should be on a per object basis, not per class.

    $this->foo(); should work,
    $this->object->foo() should not.

    While this would obvious cause the example code to break, but that’s a good thing. It would highlight the fact that the code is actually wrong (calling a protected method on another object) and would allow it to be fixed (don’t call the method or correct it’s visibility in the class definition).

    1. Yes. Probably the example is not a good pattern. The post is more about visibility. I tried to isolate the example to show this “feature”. As you say the function must be public, indeed. But why this code works instead of throwing Fatal Error?

  3. I completely agree with you Gonzalo. Apparently the visibility is handled on class level and not on instance level. I tested it with a call on a dependency: the same class, another instance and a protected method:


    class Foo
    {
    protected $foo;

    public function setFoo (Foo $foo)
    {
    foo = $foo;
    }

    public function test()
    {
    $this->foo->work();
    }

    protected function work()
    {
    echo ‘Hello world’;
    }
    }

    $foo = new Foo;
    $bar = new Foo;

    $bar->setFoo($foo);
    $bar->test();

    Obviously the class Foo has access to its own protected work() method, but apparently because $bar and $foo are the same class, the instance of $bar has access to the protected methods of $foo.

    But hey, it’s php so nothing is a bug, it’s a feature! And when it’s not a feature, it is an effect of the implementation causing this “behaviour”.

  4. Gonzalo,

    You’re publicly accessing AnotherClass on line 19, which isn’t allowed to be publicly accessed since it’s protected. It doesn’t matter if you’re doing that inside OneClass or not, it’s public access as you instantiated it outside of OneClass.

    If you dong $this->foo(), then you’d be talking to AnotherClass from within the same instance.

    Nothing strange here. 🙂

    Thanks.

    1. But as you can see if you execute the script the code works! (even with the line 19). That’s because in PHP the visibility belongs to class not instance. That’s the strange behaviour for me. It’s a feature not a bug.

    1. Not exactly the same. In your example B doesn’t extend A. If we translate your code to PHP does’t work. It only works if B extends A. Anyway your Java example is also “Strange” to me. Why it works?

  5. It does make sense, and that’s how it was taught in school, using Java.
    Primarily, the main use case then that exemplified the case was

    public int equals(Object a) {
    return (a.privateA == this.privateA) && (a.protectedA() == this.protectedA());
    }

    public int compareTo(Object a) {
    if (a.privateA != this.privateA) {
    return this.privateA.compareTo(a.privateaA);
    }
    // etc.
    }

    So, you always need to be able to access the protected and private members of an object of the same class, regardless of your current object. Because you will often need to work on the internals to implement basic behaviours, that should not be exposed publically to everyone.

  6. I’d say in this specific case, you are right about this being a bug. This isn’t about “class level” handling of visibility though. Languages (C#, C++, C#) all work that way. In those languages however, if you try to access a protected instance member from another instance, the class of the instance you are accessing should be the same as, or derived from the class of the instance accessing it.

    In your example, you are accessing a instance of AnotherClass from an instance of OneClass. AnotherClass is not derived from OneClass (it is its superclass), so the OneClass instance should not be able to access foo().

    See http://rextester.com/CUCG89709 for an example on why it shouldn’t be allowed.

    Also: http://stackoverflow.com/questions/344503/is-there-a-way-to-reach-a-protected-member-of-another-object-from-a-derived-ty explaning why it isn’t allowed in C#

  7. I completely agree with you, it does seem like a bug and I don’t think the code should run.
    It seems that others here also agree with this thought, so maybe it should be treated as a bug and get fixed.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.