重新定义类方法或类

时间:2008-09-26 00:00:45

标签: php class methods redefine

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

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岁了。

编辑:

我应该澄清一下,由于框架限制,我无法将课程从third_party_library重命名为magical_third_party_library或其他任何内容。

就我的目的而言,是否可以在课堂上添加一个功能?我认为你可以用C#做​​一个叫做“部分类”的东西。

13 个答案:

答案 0 :(得分:38)

它被称为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\''
);

答案 1 :(得分: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());

有点原油但它有效!

答案 2 :(得分:11)

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

答案 3 :(得分: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);
 }
}

答案 4 :(得分:6)

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

答案 5 :(得分:6)

对于仍在寻找这个答案的人。

您应该将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();

答案 6 :(得分:1)

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

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

答案 7 :(得分:1)

我已经通过@JPhilly的答案修改了代码,并可以重命名已修补的类,以避免出错。

此外,我更改了用于标识即将被替换的函数的正则表达式,以适应替换后的函数名称前没有任何类访问修饰符的情况

希望有帮助。

Description: @HttpUtility.HtmlDecode(Html.Raw(Model.Description))
Keywords: @HttpUtility.HtmlDecode(Html.Raw(Model.Keywords))

这是我用来修补Wordpress插件的方式:

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

答案 8 :(得分:0)

总是使用一个新的,正确的方法扩展该类,并调用该类而不是错误的类。

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

(抱歉格式糟糕)

答案 9 :(得分:0)

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

答案 10 :(得分:0)

您可以使用runkit执行此操作。 http://php.net/runkit

答案 11 :(得分:0)

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

它并不完美,但它确实提高了扩展课程变化的可见性。如果您使用Composer之类的东西获取库,则必须将副本提交给源代码控制并在更新库时更新它。

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

答案 12 :(得分: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中使用覆盖。然而,它是一个额外的间接层,所以我尽量避免在原始类上使用它。