PHPで、実行時にクラスに新しいメソッドをアタッチする方法があるかどうか疑問に思っています。つまり、インスタンスレベルではなく、クラスに直接接続されるため、新しく作成されたすべてのインスタンスにこの新しいメソッドがあります。そのようなことは反省で行うことができますか?
ありがとう
PHPで、実行時にクラスに新しいメソッドをアタッチする方法があるかどうか疑問に思っています。つまり、インスタンスレベルではなく、クラスに直接接続されるため、新しく作成されたすべてのインスタンスにこの新しいメソッドがあります。そのようなことは反省で行うことができますか?
ありがとう
はい、できます。
以下は、php5.4.xで実行時にメソッドを作成する方法です。
匿名関数は、5.3.xから開始されたClosureクラスで表されます。5.4.xから、Closure :: bind静的メソッドを追加して、無名関数を特定のオブジェクトまたはクラスにバインドします。
例:
class Foo {
private $methods = array();
public function addBar() {
$barFunc = function () {
var_dump($this->methods);
};
$this->methods['bar'] = \Closure::bind($barFunc, $this, get_class());
}
function __call($method, $args) {
if(is_callable($this->methods[$method]))
{
return call_user_func_array($this->methods[$method], $args);
}
}
}
$foo = new Foo;
$foo->addBar();
$foo->bar();
全部遊んでみました。潜在的にできることReflectionClass
は、既存のメソッドを置き換えることだけのようです。しかし、それでも間接的になります。
私は実際には、動的クラスが存在するクラスベース言語を知りません(それでも、私の知識はかなり限られています)。私はそれがプロトタイプベースの言語(javascript、ruby、smalltalk)でのみ行われるのを見てきました。代わりに、 PHP 5.4でできることは、既存のオブジェクトに新しいメソッドを使用Closure
して追加することです。
これは、任意のオブジェクトに対してそのような倒錯を実行できるようにするクラスです。
class Container
{
protected $target;
protected $className;
protected $methods = [];
public function __construct( $target )
{
$this->target = $target;
}
public function attach( $name, $method )
{
if ( !$this->className )
{
$this->className = get_class( $this->target );
}
$binded = Closure::bind( $method, $this->target, $this->className );
$this->methods[$name] = $binded;
}
public function __call( $name, $arguments )
{
if ( array_key_exists( $name, $this->methods ) )
{
return call_user_func_array( $this->methods[$name] , $arguments );
}
if ( method_exists( $this->target, $name ) )
{
return call_user_func_array(
array( $this->target, $name ),
$arguments
);
}
}
}
これを使用するには、コンストラクターに既存のオブジェクトを提供する必要があります。使用法の小さな例を次に示します。
class Foo
{
private $bar = 'payload';
};
$foobar = new Foo;
// you initial object
$instance = new Container( $foobar );
$func = function ( $param )
{
return 'Get ' . $this->bar . ' and ' . $param;
};
$instance->attach('test', $func);
// setting up the whole thing
echo $instance->test('lorem ipsum');
// 'Get payload and lorem ipsum'
正確にはあなたが望むものではありませんが、AFAIKこれはあなたが得ることができる限り近いです。
ドキュメントのcreate_function()を確認しましたか?をオーバーロードすることで、目的の結果が得られる場合もあります。
これは、runkit拡張機能のrunkit_method_add()で可能です。ただし、これを本番環境で使用する場合は注意が必要です。
例:
<?php
class Example {}
$e = new Example();
runkit_method_add(
'Example',
'add',
'$num1, $num2',
'return $num1 + $num2;',
RUNKIT_ACC_PUBLIC
);
echo $e->add(12, 4);
以下の2つの方法のいずれかを使用することもできます。
function method1()
{
echo "In method one.";
}
function method2()
{
echo "In method two.";
}
class DynamicClass
{
function __construct(){
$function_names = ['method1'];
foreach ($function_names as $function_name) {
if (function_exists($function_name)) {
$this->addMethod($function_name);
}
}
}
function addMethod($name)
{
$this->{$name} = Closure::fromCallable($name);
}
public function __call($name, $arguments)
{
return call_user_func($this->{$name}, $arguments);
}
}
$obj = new DynamicClass();
//Call method1 added in constructor
$obj->method1();
//Add method
$obj->addMethod('method2');
$obj->method2();