重新定义类方法或类
有没有办法重新定义一个类或它的一些方法,而不使用典型的inheritance? 例如:
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的inheritance者。 而且,虽然它的创build者似乎已经放弃了它(它已经声明它不兼容PHP 5.2和更高版本),但现在似乎有一个新的家和维护者 。
我仍然不能说我是其方法的粉丝 。 通过评估代码string进行修改在我看来一直是潜在的危险和难以debugging。
尽pipe如此, runkit_method_redefine
似乎是你正在寻找的,它的一个使用的例子可以在/tests/runkit_method_redefine.phpt
中的/tests/runkit_method_redefine.phpt
中find:
runkit_method_redefine('third_party_library', 'buggy_function', '', 'return \'good result\'' );
runkit似乎是一个很好的解决scheme,但默认情况下它并未启用,部分内容仍然是实验性的。 所以我一起入侵了一个在类文件中replace函数定义的小类。 用法示例:
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());
有点粗糙,但它的作品!
是的,这就是所谓的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相当详细。
为了完整起见 – 通过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); } }
还有一个新的,适当的方法,并且把这个class级叫做class级,而不是一个class级。
class my_better_class Extends some_buggy_class { function non_buggy_function() { return 'good result'; } }
(对不起蹩脚的格式)
Zend Studio和PDT(基于Eclipse的eclipse)有一些内置的应用工具。 但是没有内build的方法可以做到这一点。
你也不想在你的系统中有糟糕的代码。 因为它可能被错误地要求。
对于那些仍在寻找这个答案的人来说。
您应该将扩展与命名空间结合使用。
喜欢这个:
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();
如果图书馆明确地创build了不好的类而不使用定位符或依赖系统,那么你是不幸的。 除非子类化,否则无法在另一个类上重写方法。 解决scheme可能是创build修复该库的修补程序文件,以便升级库并重新应用修补程序来修复该特定的方法。
你可以用runkit来做到这一点。 http://php.net/runkit
你可以制作一个库类的副本,除了类名之外,其余内容都是一样的。 然后覆盖重命名的类。
这不是完美的,但它确实提高了扩展类的变化的可见性。 如果您使用Composer等工具读取库,则必须将副本提交到源代码pipe理,并在更新库时进行更新。
在我的情况下,这是一个旧版本的https://github.com/bshaffer/oauth2-server-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(); } }
您可能出于某种原因使用私有variables,但是只能通过扩展类中的类或逻辑来访问该函数。 同样,你可能想让同一个类的不同对象有不同的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'];
作为一个方面说明,如果你这样做所有的类的function,并使用像public static $functions['function_name'] = function(...){...};
的基于string的键/值存储区public static $functions['function_name'] = function(...){...};
这可以用于反思。 虽然PHP不像其他语言那么多,但因为您已经可以获取类和函数名称,但是您可以节省一些处理,而且您的类的将来用户可以在PHP中使用覆盖。 然而,这是一个额外的间接水平,所以我会尽可能避免在原始类上使用它。