Есть ли способ переопределить класс или некоторые из его методов без использования типичного наследования? Например:
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# с помощью так называемого «частичного класса».
Можете ли вы переименовать исходный класс багги? Переименуйте его в buggy third_party и расширьте его самостоятельно, присвоив своему классу исходное имя.






Zend Studio и PDT (ide на основе eclipse) имеют несколько встроенных инструментов рефрактора. Но для этого нет встроенных методов.
Также вы вообще не захотите иметь плохой код в вашей системе. Поскольку это могло быть вызвано ошибкой.
Он называется исправление обезьяны. Но в PHP нет встроенной поддержки.
Хотя, как указывали другие, библиотека runkit доступен для добавления поддержки языка и является преемником классный набор. И хотя казалось, что его создатель был заброшенный (заявив, что он несовместим с PHP 5.2 и более поздними версиями), теперь у проекта действительно есть новый дом и сопровождающий.
Я все еще не могу сказать, что я фанат своего подхода. Внесение изменений путем оценки строк кода всегда казалось мне потенциально опасным и трудным для отладки.
Тем не менее, runkit_method_redefine кажется тем, что вы ищете, и пример его использования можно найти в /tests/runkit_method_redefine.phpt в репозитории:
runkit_method_redefine('third_party_library', 'buggy_function', '',
'return \'good result\''
);
это было принято как ответ: означает ли это, что PHP поддерживает это? было бы неплохо получить дополнительную информацию.
@nickf: «Я не верю» - это отрицание; поэтому OP означает «не поддерживается в PHP». Насколько я могу судить, PHP не поддерживает исправление обезьян.
@Piskvor, ну да - я могу читать, но было любопытно, что этот ответ был принят по сравнению с другими, которые предлагали какую-то форму руководства.
Да, он называется 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 об этом довольно многословен.
Обычно это работает, однако я не могу изменить имя класса из-за того, как настроен фреймворк, который я использую.
Тогда ответ: вы не можете.
Это не работает, когда базовый класс вызывает одну из функций.
ОП спросил, как это сделать «без типичного наследования». то есть он ищет что-то вроде Java Reflection
Всегда существует расширение класса с помощью нового, правильного метода и вызов этого класса вместо ошибочного.
class my_better_class Extends some_buggy_class {
function non_buggy_function() {
return 'good result';
}
}
(Извините за дерьмовое форматирование)
Вы прочитали вопрос? Он совершенно ясно понимал, что не сможет использовать это решение.
не имеет значения - мне это помогает. :П
Если библиотека явно создает плохой класс и не использует локатор или систему зависимостей, вам не повезло. Невозможно переопределить метод другого класса, если вы не создаете подкласс. Решением может быть создание файла исправления, который исправляет библиотеку, чтобы вы могли обновить библиотеку и повторно применить исправление, чтобы исправить этот конкретный метод.
Возможно, вы сможете сделать это с помощью runkit. http://php.net/runkit
Для полноты - исправление обезьяны доступно в PHP через Runkit. Подробнее см. 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);
}
}
Это идеально подходит для обертывания основного класса. вы также можете передать конструктору исходную переменную php и заменить ее на $ this. (удобно для основного класса, не очень удобно для множества экземпляров)
Я хотел бы увидеть пример использования этого
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());
Немного грубо, но это работает!
это поездка! оператор eval затем определяет класс с замененной функцией? так что все, что мне нужно сделать, это создать экземпляр объекта и запустить его?
Да сэр! Это все, что вам нужно сделать.
@SeanDowney, это кажется почти простой правдой. Этот класс работает из коробки? Получили ли вы ожидаемые результаты @SeanDowney? Сделал бы мою жизнь черт возьми легче.
@JPhilly Я пробовал этот подход, но получаю фатальную ошибку: невозможно повторно объявить класс. Где мне запустить код eval?
Вы не должны загружать класс в первую очередь, поэтому вам все равно нужно контролировать, требовать ли класс или нет (если он загружается автоматически, вам повезло, если нет, это не сработает) Также этот класс можно было бы значительно улучшить (в регулярном выражении отсутствуют проверки нескольких пробелов), и это не сработало бы, если бы в классе были напечатаны части html в его методах или теги php, открытые / закрытые в середине класса
Вы можете сделать копию класса библиотеки со всем тем же, кроме имени класса. Затем переопределите этот переименованный класс.
Это не идеально, но улучшает видимость изменений расширяющегося класса. Если вы получите библиотеку с помощью чего-то вроде Composer, вам придется зафиксировать копию в системе управления версиями и обновить ее при обновлении библиотеки.
В моем случае это была старая версия https://github.com/bshaffer/oauth2-server-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();
это интересное решение. единственный недостаток, который я вижу, это когда вам нужно изменить поведение исходного класса в тех местах, где он используется.
Вы спасли жизнь моему проекту :). Спасибо за идею! В моей структуре я определил FrameworkFunction \ RestrictContent, и я разрешаю другим разработчикам выполнять CustomFunction \ RestrictContent, когда им нужно что-то перезаписать, не затрагивая основной код структуры. Затем, когда я использую if (class_exists ()), чтобы проверить, было ли реализовано новое пространство имен, и использовать его, в противном случае по умолчанию возвращается класс фреймворка.
Поскольку у вас всегда есть доступ к базовому коду в 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. Однако это еще один дополнительный уровень косвенности, поэтому я бы по возможности избегал его использования в примитивных классах.
Я изменил код из ответа @JPhilly и дал возможность переименовать пропатченный класс, чтобы избежать ошибок.
Кроме того, я изменил регулярное выражение, которое идентифицирует функцию, которая должна быть заменена, чтобы соответствовать случаям, когда замененная функция не имеет модификаторов доступа к классу перед ее именем.
Надеюсь, это поможет.
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]);
// capture the function with its body and replace it with the new function
if ( preg_match('/((private|protected|public)?\s?function ' . $func_name .'[\w\W\n]+?)(private|protected|public|function|class)/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 renameClass($old_name, $new_name) {
$new_code = str_replace("class $old_name ", "class $new_name ", $this->_code);
$this->setCode($new_code);
}
function getCode() {
return $this->_code;
}
}
Вот как я использовал его для исправления плагина Wordpress:
$objPatch = new Patch(ABSPATH . 'wp-content/plugins/a-plugin/code.php');
$objPatch->renameClass("Patched_AClass", "Patched_Patched_AClass"); // just to avoid class redefinition
$objPatch->redefineFunction("
function default_initialize() {
echo 'my patched function';
}");
eval($objPatch->getCode());
$result = new Patched_AClass();
PHP этого не поддерживает. Вы можете расширить класс и использовать его повторно. Вот и все. Извиняюсь.