Переопределить класс методов или классов

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

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 или что-то еще из-за ограничений рамки.

В моих целях можно было бы просто добавить функцию в класс? Я думаю, вы можете сделать это в С# с чем-то, называемым "частичным классом".

Ответ 1

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

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

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

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

runkit_method_redefine('third_party_library', 'buggy_function', '',
    'return \'good result\''
);

Ответ 2

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 новый код:

eval($objPatch->getCode());

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

Ответ 3

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

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

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

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

PHP довольно подробный.

Ответ 4

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

Ответ 5

Как обернуть его в другой класс, например

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

В любом случае расширение класса с помощью нового, правильного метода и вызов этого класса вместо багги.

class my_better_class Extends some_buggy_class {
    function non_buggy_function() {
        return 'good result';
    }
}

(Извините за дрянное форматирование)

Ответ 7

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

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

вот так:

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();

Ответ 8

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

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

Ответ 9

Если библиотека явно создает плохой класс и не использует систему локатора или зависимостей, вам не повезло. Невозможно переопределить метод в другом классе, если вы не подклассы. Решением может быть создание файла исправления, который исправляет библиотеку, поэтому вы можете обновить библиотеку и повторно применить исправление, чтобы исправить этот конкретный метод.

Ответ 10

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

Ответ 11

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

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

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

Ответ 12

Поскольку у вас всегда есть доступ к базовому коду в 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. Это, однако, один дополнительный уровень косвенности, поэтому я бы избегал использовать его на примитивных классах, где это возможно.