オンザフライでphpオブジェクトに新しいメソッドを追加する方法は?


98

「オンザフライ」でオブジェクトに新しいメソッドを追加するにはどうすればよいですか?

$me= new stdClass;
$me->doSomething=function ()
 {
    echo 'I\'ve done something';
 };
$me->doSomething();

//Fatal error: Call to undefined method stdClass::doSomething()

これは特にクラスを使用せずにですか?
トーマスピーター2011

1
__callを使用できます。ただし、__ callは使用しないでください。オブジェクトの動作を動的に変更することは、コードを読みにくく、保守しにくくする非常に簡単な方法です。
GordonM 2017年

次のツールを使用できます:packagist.org/packages/drupol/dynamicobjects
Pol Dellaiera '19

回答:


94

__callこれを利用できます:

class Foo
{
    public function __call($method, $args)
    {
        if (isset($this->$method)) {
            $func = $this->$method;
            return call_user_func_array($func, $args);
        }
    }
}

$foo = new Foo();
$foo->bar = function () { echo "Hello, this function is added at runtime"; };
$foo->bar();

6
非常に、非常に巧妙ですが、現実世界のプロジェクトIMOには無骨です!(+1で匿名の反対投票に対抗)
Pekka

2
@pekka-しかし、どのように正確に、それは不器用ですか?確かに、私がPHPで実行時にオブジェクトを拡張する専門家であると言っているわけではありませんが、正直に言って、私はそれについて多くの誤りがあるとは言えません。(たぶん私は味が悪いだけです)
karim79

16
その場合は、is_callableチェックも追加します(そのため、誤って文字列を呼び出そうとしないでください)。また、メソッドが見つからない場合はBadMethodCallException()をスローするので、メソッドが返されず、正常に返されたと思います。また、関数の最初のパラメーターをオブジェクト自体にしてarray_unshift($args, $this);から、メソッド呼び出しの前にbeforeを実行すると、関数は明示的にバインドする必要なくオブジェクトへの参照を取得できます...
ircmaxell

3
人々は要点を見逃していると思います。元々の要件はクラスのないオブジェクトを使用することでした。
トーマスピーター2011

1
isset()テストを完全に削除することをお勧めします。その関数が定義されていない場合、おそらくサイレントモードで戻りたくないからです。
Alexis Wilke 2013年


20

実行時に新しいメソッドを追加できるようにするために単に__callを使用すると、それらのメソッドが$ thisインスタンス参照を使用できないという大きな欠点があります。追加されたメソッドがコードで$ thisを使用しない限り、すべてがうまくいきます。

class AnObj extends stdClass
{
    public function __call($closure, $args)
    {
        return call_user_func_array($this->{$closure}, $args);
    }
 }
 $a=new AnObj();
 $a->color = "red";
 $a->sayhello = function(){ echo "hello!";};
 $a->printmycolor = function(){ echo $this->color;};
 $a->sayhello();//output: "hello!"
 $a->printmycolor();//ERROR: Undefined variable $this

この問題を解決するために、この方法でパターンを書き換えることができます

class AnObj extends stdClass
{
    public function __call($closure, $args)
    {
        return call_user_func_array($this->{$closure}->bindTo($this),$args);
    }

    public function __toString()
    {
        return call_user_func($this->{"__toString"}->bindTo($this));
    }
}

このようにして、インスタンス参照を使用できる新しいメソッドを追加できます

$a=new AnObj();
$a->color="red";
$a->sayhello = function(){ echo "hello!";};
$a->printmycolor = function(){ echo $this->color;};
$a->sayhello();//output: "hello!"
$a->printmycolor();//output: "red"

12

更新:ここに示すアプローチには大きな欠点があります。新しい関数はクラスの完全修飾メンバーではありません。$thisこの方法で呼び出された場合、メソッドには存在しません。つまり、オブジェクトインスタンスからデータまたは関数を操作する場合は、オブジェクトをパラメーターとして関数に渡す必要があります。また、これらの関数からクラスにアクセスしprivateたりprotected、クラスのメンバーにアクセスしたりすることはできません。

新しい匿名関数を使用した良い質問と賢いアイデア!

興味深いことに、これは機能します。

$me->doSomething();    // Doesn't work

関数自体の call_user_funcによって:

call_user_func($me->doSomething);    // Works!

機能しないのは「正しい」方法です。

call_user_func(array($me, "doSomething"));   // Doesn't work

そのように呼び出された場合、PHPではメソッドをクラス定義で宣言する必要があります。

これは private/ public/protected可視性の問題ですか?

更新:いいえ。クラス内からでも通常の方法で関数を呼び出すことは不可能であるため、これは可視性の問題ではありません。実際の関数を渡すことcall_user_func()は、私がこれを機能させるように見える唯一の方法です。


2

関数を配列に保存することもできます:

<?php
class Foo
{
    private $arrayFuncs=array();// array of functions
    //
    public function addFunc($name,$function){
        $this->arrayFuncs[$name] = $function;
    }
    //
    public function callFunc($namefunc,$params=false){
        if(!isset($this->arrayFuncs[$namefunc])){
            return 'no function exist';
        }
        if(is_callable($this->arrayFuncs[$namefunc])){
            return call_user_func($this->arrayFuncs[$namefunc],$params);
        }
    }

}
$foo = new Foo();

//Save function on array variable with params
$foo->addFunc('my_function_call',function($params){
    return array('data1'=>$params['num1'],'data2'=>'qwerty','action'=>'add');
});
//Save function on array variable
$foo->addFunc('my_function_call2',function(){
    return 'a simple function';
});
//call func 1
$data = $foo->callFunc('my_function_call',array('num1'=>1224343545));
var_dump($data);
//call func 2
$data = $foo->callFunc('my_function_call2');
var_dump($data);
?>

1

evalでこれを行う方法を確認するには、githubで入手できる私のPHPマイクロフレームワークHalcyonをご覧ください。十分に小さいため、問題なく理解できるはずです。HalcyonClassMungerクラスに集中してください。


私はあなたがPHP 5.3.0未満で実行しているため(そして私のコードもそうである)と想定しています。
ivans、

誰かが反対票についてコメントしたいかどうかを尋ねるのは無意味だと思います...
ivans

それはおそらく、ここでいくつかの大量の反対投票が行われていることです。しかし、多分あなたはあなたがしたことについて少し詳しく述べたいですか?ご覧のとおり、これはまだ明確な答えが出ていない興味深い質問です。
ペッカ

1

__callソリューションがなければ、bindTo(PHP> = 5.4)を使用して、次のように$thisバインドされたメソッドを呼び出すことができます$me

call_user_func($me->doSomething->bindTo($me, null)); 

完全なスクリプトは次のようになります。

$me = new stdClass;
// Property for proving that the method has access to the above object:
$me->message = "I\'ve done something"; 
$me->doSomething = function () {
    echo $this->message;
};
call_user_func($me->doSomething->bindTo($me)); // "I've done something"

または、バインドされた関数を変数に割り当ててから、それを呼び出さずに呼び出すこともできcall_user_funcます。

$f = $me->doSomething->bindTo($me);
$f(); 

0

Stackoverflowにも同様の投稿がありますも、これは特定のデザインパターンを実装することによってのみ達成できることをています。

他の唯一の方法は、実験的なphp拡張であるclasskitを使用することです。(投稿にも)

はい、それが定義された後、PHPクラスにメソッドを追加することが可能です。「実験的」拡張機能であるクラスキットを使用したい。この拡張機能はデフォルトで有効になっていないようですが、カスタムPHPバイナリをコンパイルできるか、WindowsでPHP DLLをロードできるかによって異なります(たとえば、DreamhostではカスタムPHPバイナリが許可されており、セットアップは非常に簡単です) )。


0

karim79の回答は機能しますが、メソッドのプロパティ内に匿名関数を格納し、彼の宣言は同じ名前の既存のプロパティを上書きするか、既存のプロパティがprivate致命的なエラーオブジェクトを使用できなくなっている場合は機能しません。メソッドインジェクタセッターは、トレイトを使用してすべてのオブジェクトに自動的に追加できます。

PSもちろん、これはSOLIDのオープンクローズの原則に違反するため、使用すべきではないハックです。

class MyClass {

    //create array to store all injected methods
    private $anon_methods = array();

    //create setter to fill array with injected methods on runtime
    public function inject_method($name, $method) {
        $this->anon_methods[$name] = $method;
    }

    //runs when calling non existent or private methods from object instance
    public function __call($name, $args) {
        if ( key_exists($name, $this->anon_methods) ) {
            call_user_func_array($this->anon_methods[$name], $args);
        }
    }

}


$MyClass = new MyClass;

//method one
$print_txt = function ($text) {
    echo $text . PHP_EOL;
};

$MyClass->inject_method("print_txt", $print_txt);

//method
$add_numbers = function ($n, $e) {
    echo "$n + $e = " . ($n + $e);
};

$MyClass->inject_method("add_numbers", $add_numbers);


//Use injected methods
$MyClass->print_txt("Hello World");
$MyClass->add_numbers(5, 10);

使用してはいけないハッキングの場合は、回答として投稿しないでください。
miken32
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.