如何在运行中向php对象添加新方法?


98

如何“动态”向对象添加新方法?

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

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

这是专门不使用类的吗?
thomas-peter

1
您可以使用__call。但是请不要使用__call。动态更改对象的行为是使代码无法读取和不可维护的非常简单的方法。
GordonM

Answers:


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 2010年

2
@pekka-但是,它到底是怎么回事?当然,我并不是说我在使用PHP运行时扩展对象方面是专家,但是老实说我不能说我对此有很多看法。(也许我只是口味不好)
karim79 2010年

16
我还要在其中添加一个is_callable检查(因此,请不要意外地尝试调用字符串)。如果找不到方法,也将抛出BadMethodCallException(),因此您不会让它返回并认为它确实返回了。另外,将函数的第一个参数array_unshift($args, $this);设为对象本身,然后在方法调用之前进行操作,以使函数无需明确绑定即可获取对该对象的引用...
ircmaxell,2010年

3
我认为人们没有抓住重点,最初的要求是使用无类对象。
thomas-peter

1
实际上,我建议您完全删除isset()测试,因为如果未定义该函数,则可能不想静默返回。
亚历克西斯·威尔克


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以这种方式调用时,方法中不存在。这意味着,如果要使用对象实例中的数据或函数,则必须将对象作为参数传递给函数!此外,您将无法访问privateprotected从这些函数类的成员。

使用新的匿名函数,很好的问题和聪明的主意!

有趣的是,这可行:替换

$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进行此操作,您可以看一下我的PHP微框架Halcyon,该框架可在github上找到。它足够小,您应该可以毫无问题地解决它-专注于HalcyonClassMunger类。


我假设(我的代码也是如此)您正在PHP <5.3.0上运行,因此需要大量的技巧和技巧来使其正常工作。
ivans 2010年

我想问是否有人要对下降投票发表评论是没有意义的……
ivans 2010年

可能是,这里发生了一些大规模的投票表决。但是,也许您想详细说明您所做的事情?如您所见,这是一个有趣的问题,尚无确切答案。
Pekka 2010年

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上也有类似的帖子也指出只有通过实现某些设计模式才能实现这一点。

唯一的其他方法是通过使用classkit(一个实验性的php扩展名)。(也在帖子中)

是的,可以在定义后向PHP类添加方法。您要使用classkit,它是“实验”扩展。似乎默认情况下未启用此扩展,因此它取决于您是否可以在Windows上编译自定义PHP二进制文件或加载PHP DLL(例如Dreamhost允许自定义PHP二进制文件,并且它们很容易设置) )。


0

karim79答案有效,但是它在方法属性中存储了匿名函数,并且如果现有属性private导致致命错误对象不可用,他的声明可以覆盖同名的现有属性,或者不起作用,我认为将它们存储在单独的数组中并使用setter是更干净的解决方案。可以使用特征自动将方法注入器设置器添加到每个对象。

PS当然,这是不应该使用的hack,因为它违反了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);

如果这是不应该使用的hack,则不应将其发布为答案。
miken32 '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.