题
有没有办法在不使用典型继承的情况下重新定义类或其某些方法?例如:
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没有原生支持。
尽管如其他人也指出的那样, runkit库可用于添加对该语言的支持并且是 classkit 的继承者。而且,虽然它的创建者似乎已经放弃了(声称它不兼容)在PHP 5.2及更高版本中,该项目现在似乎有一个新的家和维护者。
我仍然不能说我是粉丝。通过评估代码串进行修改在我看来总是有潜在危险并且难以调试。
仍然, runkit_method_redefine
似乎是您正在寻找的,其使用示例可以在 /tests/runkit_method_redefine.phpt
:
runkit_method_redefine('third_party_library', 'buggy_function', '',
'return \'good result\''
);
其他提示
runkit似乎是一个很好的解决方案,但它默认情况下没有启用,部分仍然是实验性的。所以我一起攻击了一个小类,它取代了类文件中的函数定义。用法示例:
class Patch {
private runkit似乎是一个很好的解决方案,但它默认情况下没有启用,部分仍然是实验性的。所以我一起攻击了一个小类,它取代了类文件中的函数定义。用法示例:
$objPatch = new Patch('path_to_class_file.php');
$objPatch->redefineFunction("
protected function foo(\$arg1, \$arg2)
{
return \$arg1+\$arg2;
}");
然后包括要修改的类并重新定义其方法:
eval($objPatch->getCode());
然后评估新代码:
<*>
有点原油但它有效!
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;
}
}
然后包括要修改的类并重新定义其方法:
<*>然后评估新代码:
<*>有点原油但它有效!
为了完整起见,可以通过 runkit 在PHP中获得猴子修补程序。有关详细信息,请参阅 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);
}
}
是的,它被称为 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非常冗长。
对于仍在寻找这个答案的人。
像这样: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();
Zend Studio和PDT(基于eclipse的ide)有一些内置的refractoring工具。但是没有内置的方法可以做到这一点。
此外,您根本不希望系统中存在错误的代码。因为它可能被错误地调用。
如果库显式创建了坏类而没有使用定位器或依赖系统,那你就不走运了。除非你是子类,否则无法覆盖另一个类的方法。 解决方案可能是创建修复库的补丁文件,以便您可以升级库并重新应用补丁来修复该特定方法。
您可以使用runkit执行此操作。 http://php.net/runkit
总是使用一个新的,正确的方法扩展该类,并调用该类而不是错误的类。
class my_better_class Extends some_buggy_class {
function non_buggy_function() {
return 'good result';
}
}
(抱歉格式糟糕)
您可以复制库类,除了类名之外的所有内容都相同。然后覆盖重命名的类。
它并不完美,但确实提高了扩展类更改的可见性。如果您使用Composer之类的东西获取库,则必须将副本提交给源代码控制并在更新库时更新它。
就我而言,它是旧版本的 https://github.com/bshaffer/的oauth2服务器的PHP 。我修改了库的自动加载器来取代我的类文件。我的类文件采用了原始名称,并扩展了其中一个文件的复制版本。
由于您始终可以访问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中使用覆盖。然而,它是一个额外的间接层,所以我尽量避免在原始类上使用它。