2008-09-26 2 views
37

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

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

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

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

EDIT:

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

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

+0

PHP не поддерживает это. Вы можете расширить класс и повторно использовать его. Вот и все. Сожалею. – Till 2008-09-26 00:48:12

+0

Можете ли вы переименовать оригинальный класс багги? Переименуйте его в buggy_third_party и выделите его сами, предоставив вашему классу оригинальное имя. – gnud 2008-12-07 13:16:04

ответ

36

Это называется monkey patching. Но у PHP нет собственной поддержки.

Хотя, как указывали другие, runkit library доступен для дополнительной поддержки языка и является преемником classkit. И, казалось, его создатель abandoned (заявив, что он несовместим с PHP 5.2 и новее), теперь у проекта есть new home and maintainer.

Я все еще can't say I'm a fan своего подхода. Выполнение изменений путем оценки строк кода всегда казалось мне потенциально опасным и трудным для отладки.

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

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

Это было принято как ответ: означает ли это, что PHP поддерживает его? дополнительная информация была бы приятной. – nickf 2008-12-07 13:04:23

+0

@nickf: «Я не верю» - это отрицание; поэтому OP означает «не поддерживается в PHP». Насколько я могу судить, ** PHP не поддерживает исправление обезьян **. – Piskvor 2010-02-17 09:16:31

+0

@ Piskvor, ну да - я могу читать, но было любопытно, что этот ответ был принят по сравнению с другими, которые предлагали какую-то форму руководства. – nickf 2010-02-17 13:06:24

1

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

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

9

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

+2

Это нормально работает, но я не могу изменить имя класса из-за того, как я использую инфраструктуру. – SeanDowney 2008-09-26 00:10:10

+4

Тогда ответ: вы не можете. – Till 2008-09-26 00:13:04

+0

Почему я получаю downvoted? =) – Till 2008-09-26 00:21:55

2

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

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

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

0

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

10

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

8

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

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

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

Немного сырой, но это работает!

0

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

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

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

2

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

Вы должны использовать 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(); 
0

Поскольку у вас всегда есть доступ к базовому коду в 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(); 
    }   
} 

Вы можете по какой-либо причине использовать приват te, но тогда вы сможете получить доступ к этой функции, расширив класс или логику внутри класса. Точно так же вы можете захотеть, чтобы разные объекты одного и того же класса имели разные функции. Если это так, не используйте 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. Это, однако, один дополнительный уровень косвенности, поэтому я бы избегал использовать его на примитивных классах, где это возможно.

Смежные вопросы