Как вызвать метод grandparent без получения ошибки E_STRICT?

Иногда мне нужно выполнить метод grandparent (то есть обходить родительский метод), я знаю, что это запах кода, но иногда я не могу изменить другие классы (фреймворки, библиотеки и т.д.).

В PHP мы можем сделать что-то вроде:

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

Проблема в том, что если у вас есть ошибки E_STRICT, вы получите сообщение об ошибке:

Строгие стандарты: нестатический метод GrandParent:: grandParentMethod() не следует называть статически...

Я нашел только одно решение для этого (без удаления E_STRICT), и он просто добавляет @ для подавления ошибки.

Но что действительно уродливо, знает ли кто-нибудь лучшее решение?

Спасибо! PS: Я не могу создать экземпляр нового объекта, например:

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

потому что мне нужно вызвать мой метод бабушки и дедушки в контексте $son.

Ответ 1

Вы можете использовать ReflectionMethod- > invoke()

Пример:

<?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

Примечание. Вызываемый метод должен быть общедоступным.

Ответ 2

Вы можете вызвать прародителя напрямую по имени (не нужно ни Reflection, ни 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';
    }
}

Вызов Base::getFoo может выглядеть как статический вызов (из-за синтаксиса двоеточия :: :), но это не так. Также как parent:: не является статичным.

Вызов метода из цепочки наследования внутри класса правильно связывает значение $this, вызывает его как обычный метод, соблюдает правила видимости (например, защищен) и не является нарушением любого вида!

Поначалу это может показаться немного странным, но это способ сделать это в PHP.

Ответ 3

Вы можете использовать отдельный внутренний метод (например, _doStuff для дополнения doStuff) и вызвать его непосредственно от внука через родителя.

// Base class that everything inherits
class Grandpa  {
    protected function _doStuff() {
        // grandpa 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();

показывает

grandpa
grandpa papa
grandpa kiddo

Ответ 4

Тимо ответ работает, но я думаю, что он работает случайно больше, чем дизайн. Если вы посмотрите на коды операций для $this-> doX против parent :: doX против Grandparent :: doX, вы увидите, что Grandparent :: doX вызывается как статический метод, но PHP будет использовать $ this, который находится в области видимости

$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                                            

Из-за этого параметр $ this доступен в статическом вызове дедушки (из https://www.php.net/manual/en/language.oop5.basic.php):

Псевдопеременная $ this доступна, когда метод вызывается из контекста объекта. $ это ссылка на вызывающий объект (обычно это объект, к которому принадлежит метод, но, возможно, другой объект, если метод вызывается статически из контекста вторичного объекта). Начиная с PHP 7.0.0, статический вызов нестатического метода из несовместимого контекста приводит к тому, что $ this не определено внутри метода. Вызов нестатического метода статически из несовместимого контекста устарел с версии PHP 5.6.0. Начиная с PHP 7.0.0, статический вызов статического метода обычно не рекомендуется (даже если он вызывается из совместимого контекста). До PHP 5.6.0 такие вызовы уже вызывали строгое уведомление.