Переопределить методы класса или класс
есть ли способ переопределить класс или некоторые из его методов без использования типичного наследования? Например:
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';
}
}
Это моя точная дилемма: я обновил стороннюю библиотеку, которая нарушает мой код. Я не хочу изменять библиотеку напрямую, так как будущие обновления могут снова нарушить код. Я ищу бесшовный способ заменить метод class.
я нашел это библиотека это говорит, что он может это сделать, но я осторожен, так как ему 4 года.
EDIT:
Я должен был уточнить, что я не могу переименовать класс third_party_library
to magical_third_party_library
или что-нибудь еще из-за рамки ограничений.
для моих целей можно было бы просто добавить функцию в класс? Я думаю, вы можете сделать это на C# с чем-то, что называется "частичным классом"."
12 ответов
Это называется обезьяна ямочный. Но PHP не имеет собственной поддержки для него.
хотя, как указывали другие,библиотеки runkit доступно для добавления поддержки языка и является преемником classkit. И хотя казалось, что это было ... --12-->бросил его создателем (заявив, что он не совместим с 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 новый код:
eval($objPatch->getCode());
немного грубо, но это работает!
для полноты-исправление обезьян доступно в PHP через runkit. Подробнее см. В разделе runkit_method_redefine()
.
Да, это называется 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 довольно многословен об этом.
Как насчет упаковки его в другой класс, такой как
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);
}
}
для людей, которые все еще ищут этот ответ.
вы должны использовать выходит в сочетании с пространства имен.
такой:
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 (Eclipse based ide) имеют некоторые встроенные инструменты преломления. Но для этого нет встроенных методов.
также вы не хотели бы иметь плохой код в вашей системе вообще. Поскольку его можно было вызвать по ошибке.
Если библиотека явно создает плохой класс и не использует локатор или систему зависимостей, вам не повезло. Невозможно переопределить метод в другом классе, если вы не подкласс. Решением может быть создание файла исправления, который исправляет библиотеку, чтобы можно было обновить библиотеку и повторно применить исправление для исправления этого метода.
всегда есть расширение класса новым, правильным методом и вызов этого класса вместо багги.
class my_better_class Extends some_buggy_class {
function non_buggy_function() {
return 'good result';
}
}
(извините за дерьмовое форматирование)
вы можете сделать копию класса библиотеки, со всем тем же, кроме имени класса. Затем переопределите переименованный класс.
Это не идеально, но это улучшает видимость изменений расширяющегося класса. Если вы получаете библиотеку с чем-то вроде Composer, вам придется зафиксировать копию в source control и обновить ее при обновлении библиотеки.
в моем случае это была старая версия 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. Однако это один дополнительный уровень косвенности, поэтому я бы избегал использовать его на примитивных классах, где это возможно.