Вопрос

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

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 года.

Редактировать:

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

Для полноты картины - исправление monkey доступно в PHP через ранкит.Более подробную информацию смотрите в разделе runkit_method_redefine().

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

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

Да, это называется 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 довольно многословен по этому поводу.

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

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

вот так:

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

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

Кроме того, вы бы вообще не хотели, чтобы в вашей системе был плохой код.Поскольку к нему могли обратиться по ошибке.

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top