クラスメソッドまたはクラスを再定義する
質問
一般的な継承を使用せずにクラスまたはその一部のメソッドを再定義する方法はありますか?例えば:
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;
}
}
次に、変更するクラスを含め、そのメソッドを再定義します:
<*>次に、新しいコードを評価します:
<*>少し粗雑ですが動作します!
完全を期すために、PHPでは runkit を介してモンキーパッチを利用できます。詳細については、 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';
}
}
接頭辞&quot; sd&quot;。 ;-)
クラスを拡張してメソッドをオーバーライドする場合、メソッドの署名は元の署名と一致する必要があることに注意してください。たとえば、オリジナルが 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)には、屈折ツールが組み込まれています。ただし、これを行うための組み込みメソッドはありません。
また、システムに悪いコードを一切入れたくないでしょう。誤って呼び出される可能性があるため。
ライブラリが明示的に不良クラスを作成し、ロケーターまたは依存関係システムを使用していない場合、運が悪い。サブクラス化しない限り、別のクラスのメソッドをオーバーライドする方法はありません。 解決策は、ライブラリを修正するパッチファイルを作成して、ライブラリをアップグレードし、パッチを再適用して特定の方法を修正することです。
これは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-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();
}
}
何らかの理由でプライベート変数を使用できますが、クラスまたはクラス内のロジックを拡張することによってのみ関数にアクセスできます。同様に、同じクラスの異なるオブジェクトに異なる機能を持たせたい場合もあります。その場合、静的を使用しないでください。ただし、通常は静的にしたいので、作成された各オブジェクトのメモリ使用量を複製しないでください。 「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でオーバーライドを使用できます。ただし、これは間接レベルの1つの余分なレベルなので、可能な限りプリミティブクラスで使用することは避けます。