JavaScriptではオブジェクトに後からメソッド追加して、オブジェクトを動的に拡張できますよね。
var Object = function() {
this.foo = "foo";
}
var object = new Object();
object.getFoo = function() {
return this.foo;
}
console.log(object.getFoo()); // foo
PHP5.4からは Closure::bindTo()
を使うとこれと似たようなことができます。
class Object
{
/**
* @var \Closure[]
*/
private $methods = [];
private $foo = 'foo';
public function __set(string $name, \Closure $method)
{
$this->methods[$name] = $method->bindTo($this, self::class);
}
public function __call(string $name, array $arguments)
{
if (!array_key_exists($name, $this->methods)) {
throw new \BadMethodCallException(
'Call to undefined method ' . __CLASS__ . "::$name()"
);
}
return $this->methods[$name](...$arguments);
}
}
// オブジェクトを作る
$object = new Object();
// 後からメソッドを追加する
$object->getFoo = function () {
return $this->foo;
};
// 追加したメソッドを呼び出す
echo $object->getFoo();
ただし、カプセル化を破壊するので、使い所は計画的かつ慎重に判断したほうがいいです。
せっかく trait
が使えるので、この動的メソッド追加の処理をトレイト化すると再利用性が高まり、動的メソッド追加を実装するクラスも形式的なコードが少なくなりますね。
trait DynamicMethodDeclaration
{
/**
* @var \Closure[]
*/
private $__dynamicMethods = [];
public function addMethod(string $name, \Closure $method): void
{
$this->__dynamicMethods[$name] = $method->bindTo($this, self::class);
}
public function __call(string $name, array $arguments)
{
if (!array_key_exists($name, $this->__dynamicMethods)) {
throw new \BadMethodCallException(
'Call to undefined method ' . __CLASS__ . "::$name()"
);
}
return $this->__dynamicMethods[$name](...$arguments);
}
}
class Object
{
use DynamicMethodDeclaration;
private $foo = 'foo';
}
// オブジェクトを作る
$object = new Object();
// 後からメソッドを追加する
$object->addMethod('getFoo', function () {
return $this->foo;
});
// 追加したメソッドを呼び出す
echo $object->getFoo();