Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call grandparent method without getting E_STRICT error?

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.

like image 393
Enrique Avatar asked Oct 12 '12 00:10

Enrique


4 Answers

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.

like image 83
Timo Tijhof Avatar answered Nov 03 '22 19:11

Timo Tijhof


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.

like image 27
Dr.Molle Avatar answered Nov 03 '22 18:11

Dr.Molle


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.

like image 32
dkw Avatar answered Nov 03 '22 18:11

dkw


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
like image 1
luchaninov Avatar answered Nov 03 '22 18:11

luchaninov