「オンザフライ」でオブジェクトに新しいメソッドを追加するにはどうすればよいですか?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
__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();
PHP 7では、匿名クラスを使用できます
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
実行時に新しいメソッドを追加できるようにするために単に__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"
更新:ここに示すアプローチには大きな欠点があります。新しい関数はクラスの完全修飾メンバーではありません。この方法で呼び出された場合、_
$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()
に渡すことが、この機能を実現できる唯一の方法です。
関数を配列に保存することもできます:
<?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);
?>
__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();
Evalでこれを行う方法を確認するには、my PHP micro-framework、Halcyon、これは github で利用できます。問題なく解決できるはずです-HalcyonClassMungerクラスに集中してください。
karim79answerは動作しますが、メソッドプロパティ内に匿名関数を格納し、彼の宣言は同じ名前の既存のプロパティを上書きするか、既存のプロパティがprivate
致命的なエラーオブジェクトを使用できない原因とする別の配列に格納し、setterを使用すると、よりクリーンなソリューションになると思います。メソッドインジェクターセッターは、 traits を使用してすべてのオブジェクトに自動的に追加できます。
追伸もちろんこれはハックであり、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);
stackoverflowの同様の投稿 があり、これは特定の設計パターンの実装によってのみ達成可能であることが明らかになります。
他の唯一の方法は、実験的なphp拡張モジュールであるclasskitを使用することです。 (投稿でも)
はい。メソッドを定義した後、PHPクラスにメソッドを追加できます。 「実験的な」拡張機能であるクラスキットを使用します。ただし、この拡張機能はデフォルトでは有効になっていないようです。そのため、カスタムPHPバイナリをコンパイルできるか、WindowsでPHP DLLをロードできるかによって異なります(たとえば、Dreamhostでは許可されます)カスタムPHPバイナリであり、セットアップは非常に簡単です)。