2010-05-30 53 views
75

如何添加一個新的方法「對飛」的對象?如何將一個新方法添加到一個php對象上?

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

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

這是不是專門沒有使用類? – 2011-07-20 07:42:01

+1

您可以使用__call。但請不要使用__call。動態地改變一個對象的行爲是一個非常簡單的方法,使你的代碼不可讀和不可維護。 – GordonM 2017-01-03 14:49:20

+0

你可以使用這個工具:https://packagist.org/packages/drupol/dynamicobjects – 2017-09-19 14:45:29

回答

81

您可以利用__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

非常,非常聰明,但在現實世界項目海事組織! (和+1反擊匿名downvoter) – 2010-05-30 09:07:08

+2

@pekka - 但是,確切地說,它是如何?當然,我並不是說我是運行時在PHP中擴展對象的專家,但我真的不能說我看到了很多錯誤。 (也許我只是口味不佳) – karim79 2010-05-30 09:13:50

+0

@karim這只是一般的感覺,它不是'__call'的意圖,它可能已經被使用。從編碼風格的角度來看,我會感到不安。另一方面,它*是優雅的,可以通過錯誤檢查進行擴展(比如'$ method'不存在時),並且可以在某個核心類中定義,然後傳遞給所有對象。 – 2010-05-30 09:17:26

-1

要了解如何與EVAL做到這一點,你可以看看我的PHP微架構,太平,這是可以的github。它足夠小,您應該能夠毫無問題地找出它 - 專注於HalcyonClassMunger類。

+0

我假設(以及我的代碼),你運行在PHP <5.3.0,因此需要kludges和黑客做這項工作。 – ivans 2010-05-30 09:01:24

+0

詢問是否有人想評論downvote是沒有意義的,我想...... – ivans 2010-05-30 09:08:52

+0

這可能是,有一些質量downvoting在這裏轉轉。但也許你想詳細說明你做了什麼?正如你所看到的,這是一個有趣的問題,但沒有明確的答案。 – 2010-05-30 09:28:36

12

更新:此處顯示的方法有很大的缺點:新功能不是類的完全合格的成員;當以這種方式調用時,方法中不存在$this。這意味着,如果您想使用對象實例中的數據或函數,則必須將該對象作爲參數傳遞給該函數!此外,您將無法從這些函數訪問類的privateprotected成員。

很好的問題,巧妙的構思用新的匿名功能!

有趣的是,這個工程:

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

什麼行不通是 「正確」 的方式:

call_user_func(array($me, "doSomething")); // Doesn't work 
通過call_user_func 對函數本身更換

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

如果以這種方式調用,PHP需要將方法聲明爲類定義。

這是private/public/protected可見性問題?

更新:都能跟得上。即使在課堂內部也不可能以正常的方式調用函數,所以這不是一個可見性問題。將實際功能傳遞給call_user_func()是我看起來能夠完成這項工作的唯一方法。

1

有一個similar post on stackoverflow掃清指出,這是隻有通過一定的設計模式的實現可以實現的。

唯一的另一種方式是通過使用classkit,這是一個實驗性的php擴展。 (也在帖子中)

是的,可以在PHP類定義後向 添加方法。您 要使用classkit,這是一個 「實驗性」的延伸。看來 ,這種擴展是不是 但是默認情況下啓用,所以這取決於如果 ,您可以編輯自定義的PHP程序或Windows 負載PHP的DLL,如果決定(例如 Dreamhost的確實允許自定義 PHP二進制文件,他們'非常容易 設置)。

3

這裏有一個簡單的類,允許設置匿名函數。這是一個優化類的https://gist.github.com/nickunderscoremok/5857846

<?php 
class stdObject { 
    public function __construct(array $arguments = array()) { 
     if (!empty($arguments)) { 
      foreach ($arguments as $property => $argument) { 
       if ($argument instanceOf Closure) { 
        $this->{$property} = $argument; 
       } else { 
        $this->{$property} = $argument; 
       } 
      } 
     } 
    } 

    public function __call($method, $arguments) { 
     if (isset($this->{$method}) && is_callable($this->{$method})) { 
      return call_user_func_array($this->{$method}, $arguments); 
     } else { 
      throw new Exception("Fatal error: Call to undefined method stdObject::{$method}()"); 
     } 
    } 
} 

$person = new stdObject(array(
    "name" => "nick", 
    "age" => 23, 
    "friends" => array("frank", "sally", "aaron"), 
    "sayHi" => function() { 
     return "Hello there"; 
    } 
)); 

$person->sayHi2 = function() { 
    return "Hello there 2"; 
}; 

$person->test = function() { 
    return "test"; 
}; 

var_dump($person->name, $person->test(), $person->sayHi2()); 
?> 
+0

if($ argument instanceOf Closure){$ this - > {$ property} = $ argument;其他{ $ this - > {$ property} = $ argument; }是無意義的條件。 – Kzqai 2016-08-29 19:25:10

14

,以便允許在運行時添加新的方法具有重大的缺點,這些方法不能使用這個實例引用$使用簡單__call。 一切都很好,直到添加的方法不在代碼中使用$ 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 

爲了解決THI問題,您可以重寫的圖案以這種方式

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" 
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); 
?> 
26

隨着PHP7可以使用匿名類

$myObject = new class { 
    public function myFunction(){} 
}; 

$myObject->myFunction(); 

PHP RFC: Anonymous Classes

+1

這幫了我。非常感謝。 – 2017-10-05 09:47:18

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

我永遠不會使用它,語法是可怕的,但你可以添加具有功能的對象屬性。靜態時儘可能保存打字();

class SomeFuncs{ static function a(){ /* do something */ }} 

$o = new stdClass(); 
$o->f = SomeFuncs; 
$o->f::a(); 
0

karim79解答工作但是它存儲匿名函數方法屬性裏面,他的聲明可以覆蓋現有的同名屬性或不工作,如果現有的屬性是private導致致命的錯誤對象無法使用,我認爲它們存儲在單獨的數組和使用setter是更清潔的解決方案。使用traits可自動將方法注射器設置器添加到每個對象。

P.S.當然這是不應該使用的破解,因爲它違反了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); 
相關問題