Làm cách nào để thêm một phương thức mới vào một đối tượng "đang bay"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Làm cách nào để thêm một phương thức mới vào một đối tượng "đang bay"?
$me= new stdClass;
$me->doSomething=function ()
{
echo 'I\'ve done something';
};
$me->doSomething();
//Fatal error: Call to undefined method stdClass::doSomething()
Câu trả lời:
Bạn có thể khai thác __call
điều này:
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);
trước khi gọi phương thức, do đó chức năng được một tham chiếu đến đối tượng mà không rõ ràng cần phải ràng buộc nó ...
Với PHP 7, bạn có thể sử dụng các lớp ẩn danh
$myObject = new class {
public function myFunction(){}
};
$myObject->myFunction();
Chỉ sử dụng __call để cho phép thêm các phương thức mới trong thời gian chạy có nhược điểm lớn là các phương thức đó không thể sử dụng tham chiếu $ this instance. Mọi thứ đều hoạt động tốt, cho đến khi các phương thức được thêm vào không sử dụng $ this trong mã.
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
Để giải quyết vấn đề này, bạn có thể viết lại mẫu theo cách này
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));
}
}
Bằng cách này, bạn có thể thêm các phương thức mới có thể sử dụng tham chiếu phiên bản
$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"
Cập nhật: Cách tiếp cận được hiển thị ở đây có một thiếu sót lớn: Chức năng mới không phải là thành viên đủ điều kiện của lớp;
$this
không có trong phương thức khi được gọi theo cách này. Điều này có nghĩa là bạn sẽ phải truyền đối tượng cho hàm dưới dạng một tham số nếu bạn muốn làm việc với dữ liệu hoặc hàm từ cá thể đối tượng! Ngoài ra, bạn sẽ không thể truy cậpprivate
hoặcprotected
các thành viên của lớp từ các chức năng này.
Câu hỏi hay và ý tưởng thông minh bằng cách sử dụng các chức năng ẩn danh mới!
Thật thú vị, điều này hoạt động:
$me->doSomething(); // Doesn't work
bởi call_user_func trên chính hàm :
call_user_func($me->doSomething); // Works!
những gì không hoạt động là cách "đúng":
call_user_func(array($me, "doSomething")); // Doesn't work
nếu được gọi theo cách đó, PHP yêu cầu phương thức được khai báo trong định nghĩa lớp.
Đây có phải là vấn đề về private
/ public
/ protected
khả năng hiển thị không?
Cập nhật: Không. Không thể gọi hàm theo cách bình thường ngay cả từ bên trong lớp, vì vậy đây không phải là vấn đề về khả năng hiển thị. Chuyển chức năng thực tế sang call_user_func()
là cách duy nhất tôi có thể làm cho nó hoạt động.
bạn cũng có thể lưu các hàm trong một mảng:
<?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);
?>
Để xem cách thực hiện điều này với eval, bạn có thể xem qua vi khuôn khổ PHP của tôi, Halcyon, có sẵn trên github . Nó đủ nhỏ để bạn có thể tìm ra nó mà không gặp bất kỳ vấn đề gì - hãy tập trung vào lớp HalcyonClassMunger.
Nếu không có __call
giải pháp, bạn có thể sử dụng bindTo
(PHP> = 5.4), để gọi phương thức với $this
ràng buộc $me
như sau:
call_user_func($me->doSomething->bindTo($me, null));
Tập lệnh hoàn chỉnh có thể trông như thế này:
$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"
Ngoài ra, bạn có thể gán hàm bị ràng buộc cho một biến, rồi gọi nó mà không cần call_user_func
:
$f = $me->doSomething->bindTo($me);
$f();
Có một bài đăng tương tự về stackoverflow nói rõ rằng điều này chỉ có thể đạt được thông qua việc triển khai các mẫu thiết kế nhất định.
Cách khác duy nhất là sử dụng classkit , một phần mở rộng php thử nghiệm. (còn trong bài)
Có, có thể thêm một phương thức vào một lớp PHP sau khi nó được định nghĩa. Bạn muốn sử dụng classkit, là một tiện ích mở rộng "thử nghiệm". Tuy nhiên, có vẻ như tiện ích mở rộng này không được bật theo mặc định, vì vậy nó phụ thuộc vào việc bạn có thể biên dịch tệp nhị phân PHP tùy chỉnh hoặc tải các tệp DLL PHP nếu trên windows (ví dụ: Dreamhost cho phép các tệp nhị phân PHP tùy chỉnh và chúng khá dễ thiết lập ).
karim79 answer hoạt động tuy nhiên nó lưu trữ các hàm ẩn danh bên trong các thuộc tính phương thức và các khai báo của anh ấy có thể ghi đè các thuộc tính hiện có cùng tên hoặc không hoạt động nếu thuộc tính hiện có private
gây ra lỗi nghiêm trọng đối tượng không thể sử dụng được. Tôi nghĩ rằng lưu trữ chúng trong mảng riêng biệt và sử dụng setter là giải pháp sạch hơn. Bộ định vị bộ phun phương pháp có thể được thêm tự động vào mọi đối tượng bằng cách sử dụng các đặc điểm .
PS Tất nhiên đây là bản hack không nên sử dụng vì nó vi phạm nguyên tắc đóng mở của 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);