How to call grandparent method without getting E_STRICT error?

11,542

Solution 1

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.

Solution 2

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.

Solution 3

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.

Solution 4

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
Share:
11,542

Related videos on Youtube

Enrique
Author by

Enrique

Updated on September 15, 2022

Comments

  • Enrique
    Enrique over 1 year

    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, libraries, etc).

    In PHP we can do that with something like:

    call_user_func(array(get_parent_class(get_parent_class($childObject)), 'grandParentMethod'));
    

    The problem is that if you have E_STRICT errors enabled you will get an error like:

    Strict standards: non-static method GrandParent::grandParentMethod() should not be called statically in ...

    I've found only one solution for this (without removing E_STRICT), and it's just adding the @ to supress the error.

    But that's really ugly, does somebody know a better solution?

    Thanks ! PS: I can't instantiate a new object like:

    $grandparent = get_parent_class(get_parent_class($son));
    $gp= new $grandparent;
    $gp->grandParentMethod
    

    because I need to call my grandparent method in the context of $son.

    • a20
      a20 over 8 years
      Hey Enrique, looks like Krinkle's answer is more suitable and clearer. It also doesn't require you to set public access on grandpa class in order to access it from grandchild class ... if you agree, could you change accepted answer to Krinkle's answer?
  • Enrique
    Enrique over 11 years
    I guess we can use setAccessible for non public methods. Anyway, where is $this here? in most cases I need to use grandparent inside my son class, and granparent must change things on my $this (son) object not a new one. The weird thing is that in this case PHP (5.3.13) is not showing the E_SRICT warning when I do something like: GrandParent::method(); inside my Son class (and also it's assuming the correct $this).
  • Dr.Molle
    Dr.Molle over 11 years
    I can't tell you why the call of GrandParent::method() from within the class does not force the STRICT-warning. $this will be the object that invokes the method, $son in this case, so changes to $this will apply to $son here.
  • Marcelo Diniz
    Marcelo Diniz about 11 years
    I had the same question, but according to this official bug report, if the context is right (I assume that means 'from a subclass'), the call GrandParent::method() will set $this correctly and allow this non-static call in a static way.
  • Brian
    Brian over 8 years
    This is a much cleaner approach than the accepted answer. I didn't even know you could do this in PHP until today... Thanks @Krinkle
  • Tofandel
    Tofandel almost 3 years
    This doesn't work in static context, eg calling Base::getFoo() which is static will not bind the static context