Переопределить методы класса или класс - PullRequest
45 голосов
/ 26 сентября 2008

Есть ли способ переопределить класс или некоторые его методы без использования типичного наследования? Например:

class third_party_library {
    function buggy_function() {
        return 'bad result';
    }
    function other_functions(){
        return 'blah';
    }
}

Что я могу сделать, чтобы заменить buggy_function()? Очевидно, это то, что я хотел бы сделать

class third_party_library redefines third_party_library{
    function buggy_function() {
        return 'good result';
    }
    function other_functions(){
        return 'blah';
    }
}

Это моя точная дилемма: я обновил стороннюю библиотеку, которая нарушает мой код. Я не хочу изменять библиотеку напрямую, так как будущие обновления могут снова нарушить код. Я ищу простой способ заменить метод класса.

Я нашел эту библиотеку , которая говорит, что может это сделать, но я осторожен, так как ей 4 года.

EDIT:

Мне следовало уточнить, что я не могу переименовать класс с third_party_library на magical_third_party_library или что-либо еще из-за ограничений среды.

Для моих целей, возможно ли будет просто добавить функцию в класс? Я думаю, что вы можете сделать это в C # с помощью «частичного класса».

Ответы [ 12 ]

38 голосов
/ 26 сентября 2008

Это называется исправление обезьяны . Но PHP не имеет встроенной поддержки.

Хотя, как уже отмечали другие, библиотека runkit доступна для добавления поддержки языка и является преемницей classkit . И, хотя казалось, что его создатель забросил (заявив, что он не совместим с PHP 5.2 и более поздними версиями), проект, похоже, теперь имеет новый дом и сопровождающий 1012 *.

Я все еще не могу сказать, что я фанат этого подхода. Внесение изменений путем оценки строк кода мне всегда казалось потенциально опасным и трудным для отладки.

Тем не менее, runkit_method_redefine кажется тем, что вы ищете, и пример его использования можно найти в /tests/runkit_method_redefine.phpt в хранилище:

runkit_method_redefine('third_party_library', 'buggy_function', '',
    'return \'good result\''
);
14 голосов
/ 11 июня 2011

runkit кажется хорошим решением, но он не включен по умолчанию, и его части все еще являются экспериментальными. Поэтому я взломал небольшой класс, который заменяет определения функций в файле классов. Пример использования:

class Patch {

private $_code;

public function __construct($include_file = null) {
    if ( $include_file ) {
        $this->includeCode($include_file);
    }
}

public function setCode($code) {
    $this->_code = $code;
}

public function includeCode($path) {

    $fp = fopen($path,'r');
    $contents = fread($fp, filesize($path));
    $contents = str_replace('<?php','',$contents);
    $contents = str_replace('?>','',$contents);
    fclose($fp);        

    $this->setCode($contents);
}

function redefineFunction($new_function) {

    preg_match('/function (.+)\(/', $new_function, $aryMatches);
    $func_name = trim($aryMatches[1]);

    if ( preg_match('/((private|protected|public) function '.$func_name.'[\w\W\n]+?)(private|protected|public)/s', $this->_code, $aryMatches) ) {

        $search_code = $aryMatches[1];

        $new_code = str_replace($search_code, $new_function."\n\n", $this->_code);

        $this->setCode($new_code);

        return true;

    } else {

        return false;

    }

}

function getCode() {
    return $this->_code;
}
}

Затем включите модифицируемый класс и переопределите его методы:

$objPatch = new Patch('path_to_class_file.php');
$objPatch->redefineFunction("
    protected function foo(\$arg1, \$arg2)
    {   
        return \$arg1+\$arg2;
    }");

Затем введите новый код:

eval($objPatch->getCode());

Немного грубо, но это работает!

11 голосов
/ 07 декабря 2008

Ради полноты - патч для обезьян доступен в PHP через runkit . Подробнее см. runkit_method_redefine().

8 голосов
/ 08 декабря 2010

Как насчет того, чтобы обернуть его в другой класс, такой как

class Wrapper {
 private $third_party_library;
 function __construct() { $this->third_party_library = new Third_party_library(); }
 function __call($method, $args) {
  return call_user_func_array(array($this->third_party_library, $method), $args);
 }
}
6 голосов
/ 23 июля 2017

Для людей, которые все еще ищут этот ответ.

Вы должны использовать extends в сочетании с пространствами имен .

как это:

namespace MyCustomName;

class third_party_library extends \third_party_library {
  function buggy_function() {
      return 'good result';
  }
  function other_functions(){
      return 'blah';
  }
}

Затем, чтобы использовать его, сделайте так:

use MyCustomName\third_party_library;

$test = new third_party_library();
$test->buggy_function();
//or static.
third_party_library::other_functions();
6 голосов
/ 26 сентября 2008

Да, это называется extend:

<?php
class sd_third_party_library extends third_party_library
{
    function buggy_function() {
        return 'good result';
    }
    function other_functions(){
        return 'blah';
    }
}

Я с префиксом "sd". ; -)

Имейте в виду, что когда вы расширяете класс для переопределения методов, сигнатура метода должна соответствовать оригиналу. Так, например, если оригинал сказал buggy_function($foo, $bar), он должен соответствовать параметрам в классе, расширяющем его.

PHP довольно многословен.

1 голос
/ 26 сентября 2008

Zend Studio и PDT (ide на основе eclipse) имеют некоторые встроенные инструменты рефрактинга. Но для этого нет встроенных методов.

Также вы не хотели бы иметь плохой код в вашей системе вообще. Поскольку он может быть вызван по ошибке.

0 голосов
/ 18 октября 2017

Поскольку у вас всегда есть доступ к базовому коду в PHP, переопределите функции основного класса, которые вы хотите переопределить, следующим образом, это должно оставить ваши интерфейсы без изменений:

class third_party_library {
    public static $buggy_function;
    public static $ranOnce=false;

    public function __construct(){
        if(!self::$ranOnce){
            self::$buggy_function = function(){ return 'bad result'; };
            self::$ranOnce=true;
        }
        .
        .
        .
    }
    function buggy_function() {
        return self::$buggy_function();
    }        
}

Вы можете по какой-то причине использовать личную переменную, но тогда вы сможете получить доступ к функции только путем расширения класса или логики внутри класса. Точно так же возможно, что вы захотите, чтобы разные объекты одного и того же класса имели разные функции. Если это так, не используйте static, но обычно вы хотите, чтобы он был статическим, чтобы не дублировать использование памяти для каждого созданного объекта. Код ranOnce просто гарантирует, что вам нужно только инициализировать его один раз для класса, а не для каждого $myObject = new third_party_library()

Теперь, позже в вашем коде или другом классе - когда логика достигает точки, где вам нужно переопределить функцию - просто сделайте следующее:

$backup['buggy_function'] = third_party_library::$buggy_function;
third_party_library::$buggy_function = function(){
    //do stuff
    return $great_calculation;
}
.
.
.  //do other stuff that needs the override
.  //when finished, restore the original function
.
third_party_library::$buggy_function=$backup['buggy_function'];

В качестве примечания: если вы выполняете все функции класса таким образом и используете хранилище ключей / значений на основе строк, например public static $functions['function_name'] = function(...){...};, это может быть полезно для размышления. В PHP не так много, как в других языках, потому что вы уже можете захватывать имена классов и функций, но вы можете сохранить некоторую обработку, и будущие пользователи вашего класса могут использовать переопределения в PHP. Это, однако, один дополнительный уровень косвенности, поэтому я бы по возможности не использовал его в примитивных классах.

0 голосов
/ 25 мая 2017

Вы можете сделать копию класса библиотеки, со всем таким же, кроме имени класса. Затем переопределите этот переименованный класс.

Это не идеально, но улучшает видимость изменений в расширяющемся классе. Если вы получаете библиотеку с чем-то вроде Composer, вам придется зафиксировать копию в системе контроля версий и обновить ее при обновлении библиотеки.

В моем случае это была старая версия https://github.com/bshaffer/oauth2-server-php.. Я изменил автозагрузчик библиотеки, чтобы вместо этого получить файл моего класса. Мой файл класса взял исходное имя и расширил скопированную версию одного из файлов.

0 голосов
/ 26 сентября 2008

Вы можете сделать это с помощью runkit. http://php.net/runkit

...