web-dev-qa-db-ja.com

クラスメソッドまたはクラスの再定義

典型的な継承を使用せずにクラスまたはそのメソッドの一部を再定義する方法はありますか?例えば:

_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#でこれを行うことができると思います。

46
SeanDowney

monkey patching と呼ばれます。ただし、PHPにはネイティブサポートがありません。

ただし、他の人も指摘しているように、言語へのサポートを追加するために runkitライブラリ が利用可能で、 classkit の後継です。そして、それはその作成者によって abandoned であったように見えましたが(PHP 5.2以降)と互換性がないと述べていました)、プロジェクトは 新しいホームとメンテナー があります。

私はまだ 私がファンだとは言えない そのアプローチの。コードの文字列を評価して変更を加えることは、潜在的に危険であり、デバッグが困難であると常に思っていました。

それでも、 runkit_method_redefine はあなたが探しているもののようであり、その使用例は /tests/runkit_method_redefine.phpt リポジトリ内:

runkit_method_redefine('third_party_library', 'buggy_function', '',
    'return \'good result\''
);
38

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());

少し粗雑ですが動作します!

14
JPhilly

完全を期すために、サルのパッチはPHP through runkit で利用可能です。詳細については、runkit_method_redefine()を参照してください。

11
Till

次のような別のクラスでラップするのはどうですか

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);
 }
}
8
Newbie

まだこの答えを探している人々のために。

extendsnamespaces と組み合わせて使用​​する必要があります。

このような:

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();

はい、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はそれについてかなり冗長です。

6
Till

Zend StudioとPDT(EclipseベースのIDE)には、屈折ツールが組み込まれています。ただし、これを行うための組み込みメソッドはありません。

また、システムに不正なコードが含まれることは望ましくありません。誤って呼び出される可能性があるため。

1
Ólafur Waage

常に新しい適切なメソッドでクラスを拡張し、バグのあるクラスの代わりにそのクラスを呼び出します。

class my_better_class Extends some_buggy_class {
    function non_buggy_function() {
        return 'good result';
    }
}

(安っぽいフォーマットはごめんなさい)

0
Eric Lamb

ライブラリーが明示的に不良クラスを作成しており、ロケーターまたは依存関係システムを使用していない場合、運が悪い。サブクラス化しない限り、別のクラスのメソッドをオーバーライドする方法はありません。解決策は、ライブラリを修正するパッチファイルを作成して、ライブラリをアップグレードし、パッチを再適用してその特定の方法を修正することです。

0
MOdMac

Runkitでこれを実行できる場合があります。 http://php.net/runkit

0
Toxygene

ライブラリクラスのコピーを作成できます。クラス名以外はすべて同じです。次に、名前を変更したクラスをオーバーライドします。

完全ではありませんが、拡張クラスの変更の可視性が向上します。 Composerなどでライブラリを取得する場合、コピーをソース管理にコミットし、ライブラリを更新するときに更新する必要があります。

私の場合、古いバージョンの https://github.com/bshaffer/oauth2-server-php でした。代わりにライブラリのオートローダーを変更して、クラスファイルを取得しました。クラスファイルは元の名前を引き継ぎ、ファイルの1つのコピーバージョンを拡張しました。

0
Ben Creasy

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つの余分なレベルの間接参照なので、可能な限りプリミティブクラスで使用しないようにします。

0
Garet Claborn