2008-09-26 100 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中使用覆蓋。然而,它是一個額外的間接級別,所以我會盡可能避免在原語類上使用它。