55

有没有办法在不使用典型继承的情况下重新定义一个类或它的一些方法?例如:

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_librarytomagical_third_party_library或其他任何东西重命名。

出于我的目的,是否可以只向类添加一个函数?我认为您可以在 C# 中使用称为“部分类”的东西来做到这一点。

4

13 回答 13

44

这叫做猴子补丁。但是,PHP 没有对它的原生支持。

不过,正如其他人也指出的那样,runkit 库可用于添加对语言的支持,并且是classkit的继承者。而且,尽管它的创建者似乎已经放弃了它(声称它与 PHP 5.2 及更高版本不兼容),但该项目现在似乎有了一个新的家和维护者

我仍然不能说我是它的方法的粉丝。在我看来,通过评估代码字符串来进行修改总是有潜在的危险并且难以调试。

尽管如此,runkit_method_redefine这似乎是您正在寻找的东西,并且可以/tests/runkit_method_redefine.phpt在存储库中找到其使用示例:

runkit_method_redefine('third_party_library', 'buggy_function', '',
    'return \'good result\''
);
于 2008-09-26T00:07:31.737 回答
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($objPatch->getCode());

有点粗糙,但它的工作原理!

于 2011-06-10T21:20:58.320 回答
12

对于仍在寻找此答案的人。

您应该将extendsnamespaces结合使用。

像这样:

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();
于 2017-07-22T23:29:18.697 回答
11

为了完整起见,在 PHP 中可以通过runkit获得猴子补丁。有关详细信息,请参阅runkit_method_redefine()

于 2008-12-07T12:24:05.833 回答
9

如何将它包装在另一个类中

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);
 }
}
于 2010-12-08T07:40:12.240 回答
4

是的,它被称为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对此非常冗长。

于 2008-09-26T00:08:00.313 回答
1

Zend Studio 和 PDT(基于 eclipse 的 ide)有一些内置的折射工具。但是没有内置的方法可以做到这一点。

此外,您根本不希望系统中有错误的代码。因为它可能会被错误地调用。

于 2008-09-26T00:04:10.497 回答
1

我已经从@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();
于 2020-04-26T19:44:19.103 回答
0

如果库显式地创建了坏类而不使用定位器或依赖系统,那么你就不走运了。除非您子类化,否则无法覆盖另一个类的方法。解决方案可能是创建一个修复库的补丁文件,这样您就可以升级库并重新应用补丁来修复该特定方法。

于 2008-09-26T00:14:59.847 回答
0

你也许可以用 runkit 做到这一点。http://php.net/runkit

于 2008-09-26T13:08:20.147 回答
0

您可以制作库类的副本,除了类名之外的所有内容都相同。然后覆盖该重命名的类。

它并不完美,但它确实提高了扩展类更改的可见性。如果您使用 Composer 之类的工具获取库,则必须将副本提交到源代码管理并在更新库时对其进行更新。

就我而言,它是https://github.com/bshaffer/oauth2-server-php的旧版本。我修改了库的自动加载器来获取我的类文件。我的类文件采用了原始名称并扩展了其中一个文件的复制版本。

于 2017-05-24T21:56:08.710 回答
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();
    }        
}

您可能出于某种原因使用私有变量,但您只能通过扩展类或类内的逻辑来访问该函数。同样,您可能希望同一类的不同对象具有不同的功能。如果是这样,请不要使用静态,但通常您希望它是静态的,这样您就不会重复每个对象的内存使用。“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 中使用覆盖。然而,它是一个额外的间接级别,所以我会尽可能避免在原始类上使用它。

于 2017-10-18T20:47:17.217 回答
-1

总是用一种新的、正确的方法来扩展这个类,并调用那个类而不是那个有问题的类。

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

(对不起,糟糕的格式)

于 2008-09-26T00:10:12.103 回答