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

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

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 этого не поддерживает. Вы можете расширить класс и использовать его повторно. Вот и все. Извиняюсь.

Till 26.09.2008 04:48

Можете ли вы переименовать исходный класс багги? Переименуйте его в buggy third_party и расширьте его самостоятельно, присвоив своему классу исходное имя.

gnud 07.12.2008 16:16
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
54
2
70 926
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

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 07.12.2008 16:04

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

Piskvor left the building 17.02.2010 12:16

@Piskvor, ну да - я могу читать, но было любопытно, что этот ответ был принят по сравнению с другими, которые предлагали какую-то форму руководства.

nickf 17.02.2010 16:06

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

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

SeanDowney 26.09.2008 04:10

Тогда ответ: вы не можете.

Till 26.09.2008 04:13

Это не работает, когда базовый класс вызывает одну из функций.

jor 08.03.2017 18:40

ОП спросил, как это сделать «без типичного наследования». то есть он ищет что-то вроде Java Reflection

RecursiveExceptionException 10.01.2018 20:57

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

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

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

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

Christoffer Bubach 15.08.2016 21:22

не имеет значения - мне это помогает. :П

Nick Res 09.03.2017 00:52

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

Возможно, вы сможете сделать это с помощью 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. (удобно для основного класса, не очень удобно для множества экземпляров)

GDmac 31.12.2013 16:17

Я хотел бы увидеть пример использования этого

Gerfried 23.06.2020 19:08

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 11.06.2011 01:55

Да сэр! Это все, что вам нужно сделать.

JPhilly 12.08.2011 01:06

@SeanDowney, это кажется почти простой правдой. Этот класс работает из коробки? Получили ли вы ожидаемые результаты @SeanDowney? Сделал бы мою жизнь черт возьми легче.

Theunis 09.02.2016 10:57

@JPhilly Я пробовал этот подход, но получаю фатальную ошибку: невозможно повторно объявить класс. Где мне запустить код eval?

Theunis 09.02.2016 11:38

Вы не должны загружать класс в первую очередь, поэтому вам все равно нужно контролировать, требовать ли класс или нет (если он загружается автоматически, вам повезло, если нет, это не сработает) Также этот класс можно было бы значительно улучшить (в регулярном выражении отсутствуют проверки нескольких пробелов), и это не сработало бы, если бы в классе были напечатаны части html в его методах или теги php, открытые / закрытые в середине класса

Tofandel 07.11.2019 17:18

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

Это не идеально, но улучшает видимость изменений расширяющегося класса. Если вы получите библиотеку с помощью чего-то вроде 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();

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

Garet Claborn 18.10.2017 23:55

Вы спасли жизнь моему проекту :). Спасибо за идею! В моей структуре я определил FrameworkFunction \ RestrictContent, и я разрешаю другим разработчикам выполнять CustomFunction \ RestrictContent, когда им нужно что-то перезаписать, не затрагивая основной код структуры. Затем, когда я использую if (class_exists ()), чтобы проверить, было ли реализовано новое пространство имен, и использовать его, в противном случае по умолчанию возвращается класс фреймворка.

Zach Voss 02.05.2020 17:58

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

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