11

I am confused by how PHP call method in parent-children hierarchy. Here is the code

class A {
        private function foo() {
            echo "A!";
        }
        public function test() {
            $this->foo();
        }
 }

class C extends A {
       public function foo() {
            echo 'C!';
       }
}

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

The output is A!

consider another example, but only change the foo() method visibility in class A to be public.

class A {
        public function foo() {
            echo "A!";
        }
        public function test() {
            $this->foo();
        }
 }

 class C extends A {
        public function foo() {
            echo 'C!';
        }
 }

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

This output is C!

Any explanation is welcome.

Nero
  • 1,555
  • 1
  • 13
  • 28
  • 2
    Private methods are only visible to the actual class, protected is visible only inside the class (any child class). Public is for the world to see. In your fist example calling `foo` will fail in any case except from within the class it's defined within. When you override foo in the child, it doesn't replace the private foo method because it's not visible to the C class at all. – ArtisticPhoenix Nov 02 '18 at 02:09
  • 3
    @ArtisticPhoenix thanks for comment, but I don't think that solve my confusion. Maybe you can explain in more details using above example. – Nero Nov 02 '18 at 02:11
  • 3
    @Artistic `foo` is only called from within the class that defines it. The question is why `test` sticks to calling `A::foo` instead of `C::foo`. – deceze Nov 02 '18 at 02:12
  • 1
    I tried, it's like relativity it's all depends on the reference frame of the observer. You can't think of it in terms of the Global space (space outside the classes). – ArtisticPhoenix Nov 02 '18 at 03:50

6 Answers6

8

Rule: private and final methods on an object will always be called directly, without consulting the override table.

This rule is baked into the engine:

/* Check if this calls a known method on $this */
if (opline->op1_type == IS_UNUSED && opline->op2_type == IS_CONST &&
        CG(active_class_entry) && zend_is_scope_known()) {
    zend_string *lcname = Z_STR_P(CT_CONSTANT(opline->op2) + 1);
    fbc = zend_hash_find_ptr(&CG(active_class_entry)->function_table, lcname);

    /* We only know the exact method that is being called if it is either private or final.
     * Otherwise an overriding method in a child class may be called. */
    if (fbc && !(fbc->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_FINAL))) {
        fbc = NULL;
    }
}

"Why", you ask? The answer is: because that's how it works. In language design, this is called "name hiding", and it's up to the language to specify how name hiding works. Take C++ for example. It has well-defined, and complex name hiding rules. PHP has its own rules. They're different from C++. But they're unique to PHP. This is just something you have to memorize about the language.

I admit the docs could better spell this out, however.

bishop
  • 37,830
  • 11
  • 104
  • 139
  • 1
    Perfect. It seems that there is no PHP document about name hiding? Maybe the PHP team assumes that "if you need to handle name hiding, you must have knowledge about OOP/C++/Java, etc" :D – shawn Nov 02 '18 at 04:51
2

Ok let me try [Again]

Basically both methods exist side by side.

Private by design means that this thing (method or property) is not visible or accessible or writable by anything besides the class it was defined in. So children cannot use it in any way nor modify it in any way. This is the behaviour of private.

The why (the reason) behind the concept, is to guarantee a specific functionality (or value, of properties) no matter how the class is extended. This is good because when designing the Parent class we may not know how it will be used by any extending classes, and there may be functionality that is critical and should not be changed. This gives us a way to guarantee that in our code without knowing the implementation of the children.

One reason they can exist side by side, is inside of the child class when you call $this->foo() there is no ambiguity about which foo is being called as only the public version C::foo() visible to class C (the child) class.

class A {
    private function foo() {
        echo "A!";
    }
    public function parentTest() {  //formerly A::test() 
        //if we used C::foo() from here Overriding A::foo().
        //having a concept of private would lose all meaning
        //because we would be allowing the child to modify 
        // the functionality of private A:foo()
        $this->foo();
    }
 }

class C extends A {
   public function foo() {
        echo 'C!';
   }
   public function childTest() {
       //A::foo() cannot be used from here
        //so PHP knows to use C::foo()
        $this->foo(); 
    }
}

In short there is only one Foo available to the child class C::foo(), so there is no way it can be confused with the private version A::foo() by PHP. In this way both methods can exist without the child overriding the parents method.

Don't let me confuse you more here, because in some ways the childs method does override the parents. We can see this if we call $obj->foo() from the global space. If we don't define foo in the child C then we get an error. But if we do define a public one in C it is used. Another thing that shows that it does overwrite (in some ways) is if we set the parent's copy A::foo() to be final. In this case if we try to define C::foo() we get an error as we would expect.

But (here is the important part), neither of these affect the functionality of the private stuff as far as the parent is concerned and so they do not violate the private principle.


Now for the parents call to its own private method.

This is where most people get confused and you asked:

When calling what is for all intents and purposes C::test, why does it prefer the private A::foo over the public C::foo

It doesn't because you are calling the private method A::foo() from another method A::test() in the same class class A where the private one was defined.

If it preferred the child's method over it's own, then then there would be no point in having a private visibility because we would have just violated it by allowing the child to modify the method. In other words we would be using the child's functionality instead of that marked by private.

Really it's the only sensible way that it could work. On the same token if you overwrite A::test() with the same exact code but in class C or C::test() the same call would now access the public method because that is the only one visible inside of class C where the new call came from.

Summery

This is the only logical way for private to work, if the class defining the private method was allowed to use a child definition of the method it would completely nullify the reason for having private in the first place, and I wouldn't be trying to explain it. Because it would then simply be what we call protected, which we already have.

UPDATE

So, sometimes it calls method from its own, sometimes it calls the one from parent's.

Not entirely true. Calls to the private method (foo) coming from a method defined only in the parent(A) will always access the parent(A)s private method (foo). Calls coming from a method defined in the child(C) will access either the Child(C)s method (foo), or throw an error when trying to access the Parent(A)s private method(foo).

Case1: if A:foo() is called from within A (no matter the instance) it will call it's private method regardless if there is a method from the child class that overrides foo. In this context is called from within A means called from any method defined within A and not overwritten in B. The call to foo comes from class A.

class A {
    private function foo() {
        echo "A!\n";
    }

    public function parentTest() {
        //parentTest Is not overwritten in C
        //so when C::parentTest() is called that call goes here
        //inside the A class where foo is defined.
        $this->foo();
    }
}

class C extends A {
   private function foo() {
        echo "C!\n";
   }
}

//this is called from A
//because there is no method named parentTest outside of A
(new C)->parentTest(); //A!

See the mistake your making is (new C)->parentTest(); does not physically exist in C. It's included in C by inheritance and its scope is still within the parent class when it comes to private access, because both foo and parentTest exist only in class A.

Therefore it has access to class A's private methods, because it is class A, even though the object is an instance of C.

You could remove class C altogether and the behavior would be the same.

class A {
    private function foo() {
        echo "A!\n";
    }

    public function parentTest() {
        $this->foo();
    }
}
//the path the code follows to execute is identical to the previous example.

(new A)->parentTest(); //A!

Case2: if A:foo() is called directly from a child of A and is not overwritten, you get an access to private method error. The call to foo comes from class C.

class A {
    private function foo() {
        echo "A!\n";
    }
}

class C extends A {
   public function childTest() {
        //A::foo() is not visable, and no C::foo() exists
        $this->foo();
   }
}

//this comes direct from Class C, 
//which cannot directly access Class A's private scope
(new C)->childTest(); //Error: Call to private method A::foo()

Case3: if A:foo() is called from a child of A and is overridden it will call the Child's method. is called from a child of A means any method defined within C regardless if it overrides a (public/protected) method of A. The call to foo comes from class C.

class A {
    private function foo() {
        echo "A!\n";
    }
}

class C extends A {
   public function foo() {
        echo "C!\n";
   }

   public function test() {
        //C::test() calls it's foo function
        //because the call comes from here not inside the A class
        $this->foo();
   }
}

//this is called from C, the method childTest is defined in C

(new C)->childTest(); //C!

This is true regardless of there being a public method of the same name (test) in the parent.

class A {
    private function foo() {
        echo "A!\n";
    }

    public function test() {
        //this is a public method that is overwritten as normal by C
        //when this is called the, C::test() is called as normal 
        //because of inheritance
        $this->foo();
    }
}

class C extends A {
   public function foo() {
        echo "C!\n";
   }

   public function test() {
        //C::test() calls it's foo function
        //again the call comes from here not inside the A class
        $this->foo();
   }
}

//This is called from C, because test is overwritten in C and is visible (public)

(new C)->test(); //C!

As I showed in the first example in this Update (except its backwards), you could remove the A class entirely and the way the code executes would not change:

class C{
   private function foo() {
        echo "C!\n";
   }

   public function test() {
        $this->foo();
   }
}
 //the code executes along the same path even without A
 (new C)->test(); //C!

In short: inheritance is not a simple Copy And Paste operation. The code from A is not copied and Pasted into C. Each class still exists, its just that A shares anything that is accessible with C (protected, public)

I hope that helps, that's the best I can do.

ArtisticPhoenix
  • 21,464
  • 2
  • 24
  • 38
  • When calling what is for all intents and purposes `C::test`, why does it prefer the `private` `A::foo` over the `public` `C::foo`? – deceze Nov 02 '18 at 02:15
  • It doesn't prefer it. Your thinking in terms of it over writing or replacing that method, which it doesn't do. It simply doesn't exist. – ArtisticPhoenix Nov 02 '18 at 02:47
  • 1
    Explanation like class A does not know about existence of class B seem make no sense to me especially when we're talking with my example. the way you try to prove this is wrong, because you use (new A)->test(); but in my case, it's (new C)->test(); They are different. You call parent directly and of course it won't know. In my case, it makes call from a child. Correct me if wrong. – Nero Nov 02 '18 at 03:09
  • 1
    Additionally, if you remove function foo() from class A, the one defined in child will be called. Dose this prove "class A is aware of class C's existence" ? – Nero Nov 02 '18 at 03:16
  • `Dose this prove "class A is aware of class C's existence"` No because your calling it from an instance of the child. And even though you call the private method from an child instance private overrides that normal behaviour. If it didn't there would be no point in having it. – ArtisticPhoenix Nov 02 '18 at 03:33
  • This behavior is baked into the engine, so ultimately you're trying to rationalize a design decision. It's a laudable effort, but I'm not sure it works out: the argument ends up sounding like an apology or polemics -- rather than _reason_. (Still a +1 though.) – bishop Nov 02 '18 at 04:20
  • 1
    @bishop - its the only logical way that it would work that is my point. To allow the method to be overridden, completely nullifies the reason to have private in the first place. – ArtisticPhoenix Nov 02 '18 at 04:35
  • ***Don't let me confuse you more here, because in some ways the childs method does override the parents*** We're now know the output. but this is exactly where the confusion lies on. C can override parent method foo by defining its own one, then $object_initiated_from_C->foo() will invoke the one defined in C, not A. We're all agree on this. Look back to the line $this->foo() inside test() method, the ***$this*** is an object of A. So, sometimes it calls method from its own, sometimes it calls the one from parent's. – Nero Nov 02 '18 at 05:44
2

This is the behavior in most languages (by design).

If a method is private, $this->method() will call it directly (ignore the derived class's method)

If a method is virtual (this concept comes from C++, in PHP and Java, all public/protected methods are virtual), $this->method() will call the derived class's method.

Rule is rule, it is an OOP concern. For example, in OOP we may want to make sure a method never got overridden, then we can make it private. If a developer overrides it by accident, and would hence introduce weird behaviour if the parent suddenly called the child's implementation. Thanks to @deceze

shawn
  • 4,305
  • 1
  • 17
  • 25
  • Is there an "official" reasoning for this? What I'd cobble together as an explanation would be something like: *child classes should not be aware of the existence of `private` methods, if they override them it would be by accident, and would hence introduce weird behaviour if the parent suddenly called the child's implementation*…? – deceze Nov 02 '18 at 02:23
  • Thanks. you just remind me something and it's explained in the office doc. It has the same explanation as yours. the private method has higher precedence. – Nero Nov 02 '18 at 02:23
  • @deceze yes, it is the OOP concern. I agree with your explanation – shawn Nov 02 '18 at 02:24
  • My English is not very good, maybe you can help to edit the answer :) @deceze – shawn Nov 02 '18 at 02:28
  • 1
    @deceze Yes, there is an [official reason](https://stackoverflow.com/a/53112470/2908724)... – bishop Nov 02 '18 at 04:11
  • "This is the behavior in most languages (by design)." I agree, but future readers might appreciate links to official documentation in other languages that substantiates the claim. – bishop Nov 02 '18 at 04:15
2

I think the answer lies on the official documentation. doc here

In non-static contexts, the called class will be the class of the object instance. Since $this-> will try to call private methods from the same scope

As a note, though this could eliminate my confusion, simply saying "that is the way it is" seem not enough. Because it's weird behavior. It's not as clear as the late static binding nor respect to the called scope class C (which can be verified using get_called_class() in test() method ).

Nero
  • 1,555
  • 1
  • 13
  • 28
  • Yes, this is the documented reason, but I feel it could be clarified. As unsatisfying as it may sound, that's just the way it is. :) – bishop Nov 02 '18 at 04:10
0

Private foo() is a part of class A, and A does not know about the existence of the C class. It can work without it. When extending A class, C inherits all public and protected method of A, that's why test() is available. You would call the private foo() method of A class from within the test() method, not the one from C. Extending works in one direction only, you would not be able to call methods from C from within A. A can be instantiated without C and nothing can guarantee another class will extend it.

bishop
  • 37,830
  • 11
  • 104
  • 139
Marius S
  • 267
  • 3
  • 21
  • A does not know about the existence of the C class, but why after change visibility of foo method in class A make it know about the existence of the C class? – Nero Nov 02 '18 at 02:34
  • "Extending works in one direction only". Does it? The second scenario in the OP is an example where `A::test()` calls `C::foo()`. – bishop Nov 02 '18 at 04:13
0

You already got to the answer:

foo() is available just inside A (is private) and will not be inherited in any way; so calling test() (defined just inside A) from the C scope won't have access to C::foo(), but will stick to the only scope from which it can get a valid foo method, consistently without breaking privacy/visibility rules.


Solution:

The behaviour you expect can be achieved (as you already mentioned) with the late static binding, using static::foo(), instead of $this->foo(), inside the test method.

Try changing test like this:

class A {
        private function foo() {
            echo "A!";
        }
        public function test() {
            static::foo();
        }
 }

class C extends A {
       public function foo() {
            echo 'C!';
       }
}

$c = new C();
$c->test();  // echoes "C!"

 Exaplanation:

Using static will tell the instance to change from the default behaviour; it will override the default inherited context of A, when calling test(), and will bind its own instance context C, when calling c->test()).

Using static allows to write a method that is ready for being executed in the context of an extended class, rather than in the context where it was defined. But still it will stick to the latter (the definition context) by default, if there is no extension of the class yet (meaning that $this/A is the latest context of the hierarchy that is being instantiated).


Note: I might be not exactly/academically precise (check Bishop answer for it), but I tried to re-explain it in a clearer/profane/accessible way, since I remember it was kind of counterintuitive to me when I first had to understand the Late Static Bindings. The other answers are good as well but just wanted to leave an additional help for beginners of PHP, with another point of view on it.

Community
  • 1
  • 1
Kamafeather
  • 8,663
  • 14
  • 69
  • 99