ฉันจะเพิ่มวิธีการใหม่ให้กับวัตถุ "ทันที" ได้อย่างไร
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
ฉันจะเพิ่มวิธีการใหม่ให้กับวัตถุ "ทันที" ได้อย่างไร
$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();
array_unshift($args, $this);
ก่อนการเรียกใช้เมธอดเพื่อให้ฟังก์ชันได้รับการอ้างอิงถึงวัตถุโดยไม่จำเป็นต้องผูกมัดอย่างชัดเจน ...
ด้วย PHP 7 คุณสามารถใช้คลาสที่ไม่ระบุตัวตนได้ซึ่งจะช่วยลดstdClass
ข้อ จำกัด
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
การใช้เพียง __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
ในการแก้ปัญหานี้คุณสามารถเขียนรูปแบบใหม่ได้ด้วยวิธีนี้
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();
มีโพสต์ที่คล้ายกันใน stackoverflowซึ่งทำให้ชัดเจนว่าสิ่งนี้ทำได้ผ่านการใช้รูปแบบการออกแบบบางอย่างเท่านั้น
วิธีเดียวคือการใช้classkitซึ่งเป็นส่วนขยาย php ทดลอง (ยังอยู่ในโพสต์)
ใช่เป็นไปได้ที่จะเพิ่มวิธีการลงในคลาส PHP หลังจากกำหนดแล้ว คุณต้องการใช้ classkit ซึ่งเป็นส่วนขยาย "ทดลอง" ดูเหมือนว่าส่วนขยายนี้ไม่ได้เปิดใช้งานตามค่าเริ่มต้นดังนั้นจึงขึ้นอยู่กับว่าคุณสามารถคอมไพล์ไบนารี PHP ที่กำหนดเองหรือโหลด PHP DLLs ได้หากบน windows (เช่น Dreamhost อนุญาตให้ใช้ไบนารี PHP ที่กำหนดเองและติดตั้งได้ง่ายมาก ).
คำตอบkarim79 ใช้งานได้ แต่จะเก็บฟังก์ชันที่ไม่ระบุตัวตนไว้ในคุณสมบัติของวิธีการและการประกาศของเขาสามารถเขียนทับคุณสมบัติที่มีอยู่ของชื่อเดียวกันหรือไม่ทำงานหากคุณสมบัติที่มีอยู่private
ทำให้วัตถุข้อผิดพลาดร้ายแรงใช้ไม่ได้ฉันคิดว่าการจัดเก็บไว้ในอาร์เรย์แยกต่างหากและการใช้ setter เป็นวิธีที่สะอาดกว่า วิธีการฉีดหมาสามารถเพิ่มไปยังทุกวัตถุโดยอัตโนมัติโดยใช้ลักษณะ
ปล. แน่นอนว่านี่คือการแฮ็กที่ไม่ควรใช้เนื่องจากเป็นการละเมิดหลักการปิดแบบเปิดของ 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);