Sometimes I need to execute grandparent method (that is, bypass the parent method), I know this is code smell, but sometimes I can\'t change the other classes (frameworks, l
You may use ReflectionMethod->invoke()
Example:
<?php
class Grandpa {
protected $age = 'very old';
public function sayMyAge() {
return 'sayMyAge() in Grandpa should be very old. ' .
'My age is: ' . $this->age;
}
}
class Pa extends Grandpa {
protected $age = 'less old';
public function sayMyAge() {
return 'sayMyAge() in Pa should be less old. ' .
'My age is: ' . $this->age;
}
}
class Son extends Pa {
protected $age = 'younger';
public function sayMyAge() {
return 'sayMyAge() in Son should be younger. ' .
'My age is: ' . $this->age;
}
}
$son = new Son();
$reflectionMethod = new ReflectionMethod(get_parent_class(get_parent_class($son)),
'sayMyAge');
echo $reflectionMethod->invoke($son);
// returns:
// sayMyAge() in Grandpa should be very old. My age is: younger
Note: The invoked method must be public.
Timo's answer works but I think it works by accident more than by design. If you look at the opcodes for a $this->doX vs a parent::doX vs a Grandparent::doX you can see that Grandparent::doX is invoked as a static method but PHP will use the $this that's in scope
$this->doX
17 1 EXT_STMT
2 INIT_METHOD_CALL 'doX'
3 EXT_FCALL_BEGIN
4 DO_FCALL 0
5 EXT_FCALL_END
parent::doX
18 6 EXT_STMT
7 FETCH_CLASS 514 :1
8 INIT_STATIC_METHOD_CALL $1, 'doX'
9 EXT_FCALL_BEGIN
10 DO_FCALL 0
11 EXT_FCALL_END
Grandparent::doX
19 12 EXT_STMT
13 INIT_STATIC_METHOD_CALL 'C1', 'doX'
14 EXT_FCALL_BEGIN
15 DO_FCALL 0
16 EXT_FCALL_END
The $this parameter is available in the Grandparent's static invocation because of this (from https://www.php.net/manual/en/language.oop5.basic.php):
The pseudo-variable $this is available when a method is called from within an object context. $this is a reference to the calling object (usually the object to which the method belongs, but possibly another object, if the method is called statically from the context of a secondary object). As of PHP 7.0.0 calling a non-static method statically from an incompatible context results in $this being undefined inside the method. Calling a non-static method statically from an incompatible context has been deprecated as of PHP 5.6.0. As of PHP 7.0.0 calling a non-static method statically has been generally deprecated (even if called from a compatible context). Before PHP 5.6.0 such calls already triggered a strict notice.
You can use a separate internal method (e.g. _doStuff
to complement doStuff
) and call that directly from the grandchild, through the parent.
// Base class that everything inherits
class Grandpa {
protected function _doStuff() {
// grandpa's logic
echo 'grandpa ';
}
public function doStuff() {
$this->_doStuff();
}
}
class Papa extends Grandpa {
public function doStuff() {
parent::doStuff();
echo 'papa ';
}
}
class Kiddo extends Papa {
public function doStuff() {
// Calls _doStuff instead of doStuff
parent::_doStuff();
echo 'kiddo';
}
}
$person = new Grandpa();
$person->doStuff();
echo "\n";
$person = new Papa();
$person->doStuff();
echo "\n";
$person = new Kiddo();
$person->doStuff();
shows
grandpa
grandpa papa
grandpa kiddo
You can call the grandparent directly by name (does not need Reflection, nor call_user_func
).
class Base {
protected function getFoo() {
return 'Base';
}
}
class Child extends Base {
protected function getFoo() {
return parent::getFoo() . ' Child';
}
}
class Grandchild extends Child {
protected function getFoo() {
return Base::getFoo() . ' Grandchild';
}
}
The Base::getFoo
call may look like a static call (due to the colon ::
syntax), however it is not. Just like parent::
isn't static, either.
Calling a method from the inheritance chain within a class will correctly bind the $this
value, invoke it as a regular method, honour the visibility rules (e.g. protected), and is not a violation of any kind!
This may look a bit strange at first, but, this is the way to do it in PHP.