Wednesday, September 25, 2019

oop - PHP: Why do Inherited Methods of Child Class access Parent's Private Properties?

What a curious twist in PHP's property and method inheritance. See the following example:


class A {
private $foo = 'hello';
function get() {
return $this->foo;
}
function set($val) {
$this->foo = $val;
}
}
class B extends A {
private $foo = 'world';
function get() { // try inheriting this
return $this->foo;
}
function set($val) { // try inheriting this
$this->foo = $val;
}
}

Now let's see how that echoes out if we inherit or define methods:


$b = new B();
/* If B defines getter: */
echo $b->get(); // 'world'
/* If B inherits getter: */
echo $b->get(); // 'hello'
// Enter Setter!
$b->set('cosmos');
/* If B defines setter & inherits getter: */
echo $b->get(); // 'hello'
/* If B inherits setter & defines getter: */
echo $b->get(); // 'world'
/* If B defines or inherits both: */
echo $b->get(); // 'cosmos'

So: When class B defines the getter, it returns its own private $foo ( = "world"). However if I remove the getter from class B, and class B inherits the very same method from class A, it returns instead the private $foo property from the parent class A (= "hello") instead. Likewise with setters.


I was expecting inherited methods to access the properties of the instantiated class at all times, with no reference to their ancestors' private properties. They are private for a reason, no? Private with side-effects! If I change the prop visibility to public or protected, the expected properties "world" and "cosmos" from class B are returned, whether methods are defined or inherited.




Here are object dumps for each of the above cases:


var_dump($b);
/* If B defines getter (Prop get from B): */
["foo":"B":private] · string(5) "world"
["foo":"A":private] · string(5) "hello"
/* If B inherits getter: (Prop get from A) */
["foo":"B":private] · string(5) "world"
["foo":"A":private] · string(5) "hello"
// Enter Setter!
$b->set('cosmos');
/* If B defines setter & inherits getter
* Prop set to B & get from A */
["foo":"B":private] · string(6) "cosmos"
["foo":"A":private] · string(5) "hello"
/* If B defines getter & inherits setter
* :: Prop set to A & get from B */
["foo":"B":private] · string(5) "world"
["foo":"A":private] · string(6) "cosmos"
/* If B defines both:
* :: Prop set to B & get from B */
["foo":"B":private] · string(6) "cosmos"
["foo":"A":private] · string(5) "hello"
/* If B inherits both:
* :: Prop set to A & get from A: */
["foo":"B":private] · string(5) "world"
["foo":"A":private] · string(6) "cosmos"

Can someone explain to me the logic in what's going on? Are inherited property-access methods bound to the private properties of the original method-defining parent class? I'd rather not copy-paste the same method into each child class that has private properties with a matching name.


Excuse me if the illustrations above go on ad nauseam. Had to spell this out for my own sanity huh -- unexpected behavior, unless I'm missing something obvious. (N.B. Having constructors or invoking parent::__construct() in the child class constructor has no impact on this behavior.)




Add: Now one step further. If I declare the getters protected, and create a public method in class B for getting variables. Like so:


class B {...
public function getpro_direct() {
return $this->foo;
}
public function getpro_getter() {
return $this->get();
}
...}
echo $b->getpro_direct(); // "world" (from B)
echo $b->getpro_getter(); // "hello" (from A)

And if I inherit these two methods from A instead, I get "hello" on both counts. But if I define a get() in B, I get "hello" and "world" respectively. Hello world hello, cosmos calling. I need some juice.




Edit for those of you wondering why I'm at this. The short background story: I'm working on a class hierarchy that grows in more depth in (array) properties, with each child class being a less abstract version down the same vector. (Spare you the examples.) I'm working out the smartest way to merge the child's extended properties with each consecutive parent's basis.


Private declaration was so far the only way to prevent the child class from overwriting the parent's properties, as I could have the properties of both simultaneously available via using the parent getter. That however mucks up the method inheritance, as illustrated here. I'd like to get this done as automagically as possible; preferably with a single clone_parent() method that's inherited and run in each child's constructor. I'd rather avoid spelling out separate cloners for each parent-child extension hop. (I've searched and read around a bunch here, yet to find a satisfactory solution.)


Edit2: Other approaches pending, on my actual concern (as above) that led to this experiment. To keep things simple, I'll just ditch the overlapping declaration in the child's properties and call an extend_props method that adds atop the props inherited from the arent. (Alas I hoped to have each extending class's extras shown pretty up atop, instead of lurking down below inside a method!)

No comments:

Post a Comment

hard drive - Leaving bad sectors in unformatted partition?

Laptop was acting really weird, and copy and seek times were really slow, so I decided to scan the hard drive surface. I have a couple hundr...