2008-09-26 61 views
37

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

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#中使用称为“部分类”的东西来做到这一点。

+0

PHP不支持。您可以扩展课程并重新使用它。这就对了。抱歉。 – Till 2008-09-26 00:48:12

+0

你可以重新命名原车类吗?将它重命名为buggy_third_party,然后自行发布它,为您的课程命名。 – gnud 2008-12-07 13:16:04

回答

36

它叫做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\'' 
); 
+0

这被接受为答案:这是否意味着PHP支持它?一些更多的信息会很好。 – nickf 2008-12-07 13:04:23

+0

@nickf:“我不相信”是一种否定;所以OP意味着“PHP不支持”。据我所知,** PHP不支持猴子修补**。 – Piskvor 2010-02-17 09:16:31

+0

@Piskvor,是的 - 我可以阅读,但很奇怪,这个答案被其他人提供了某种形式的指导。 – nickf 2010-02-17 13:06:24

1

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

你也不想在你的系统中有任何不好的代码。因为它可能会被错误地调用。

9

是的,这就是所谓的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是相当详细的关于它。

+2

这通常会正常工作,但我不能改变类名称,由于我如何使用框架设置 – SeanDowney 2008-09-26 00:10:10

+4

然后答案是,你不能。 – Till 2008-09-26 00:13:04

+0

我为什么会陷入低谷? =) – Till 2008-09-26 00:21:55

2

总有一种新的适当的方法来扩展这个类,并且调用这个类而不是一个错误的类。

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

(很抱歉的蹩脚格式)

0

如果库被明确创建坏类,而不是使用定位器或依赖系统你是出于运气。除非子类化,否则无法在另一个类上重写方法。 解决方案可能是创建一个修复该库的修补程序文件,以便您可以升级该库并重新应用该修补程序来修复该特定方法。

10

为了完整起见 - 通过runkit在PHP中提供猴子修补程序。详情请参阅runkit_method_redefine()

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); 
} 
} 
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新代码:

eval($objPatch->getCode()); 

有点粗糙,但它的工程!

0

你可以制作一个库类的副本,除了类名之外,其余内容都相同。然后覆盖重命名的类。

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

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

2

对于仍在寻找答案的人。

您应该使用extends结合namespaces

这样的:

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

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