有没有办法在不使用典型继承的情况下重新定义类或其某些方法?例如:
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 :(得分: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)
对于仍在寻找这个答案的人。
您应该将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();
答案 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中使用覆盖。然而,它是一个额外的间接层,所以我尽量避免在原始类上使用它。