Làm thế nào để thêm một phương thức mới vào một đối tượng php đang bay?


98

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()

Đây có phải là đặc biệt mà không cần sử dụng một lớp?
thomas-peter

1
Bạn có thể sử dụng __call. Nhưng vui lòng không sử dụng __call. Tự động thay đổi hành vi của đối tượng là một cách rất dễ dàng để làm cho mã của bạn không thể đọc được và không thể hiểu được.
GordonM

Bạn có thể sử dụng công cụ này: packagist.org/packages/drupol/dynamicobjects
Pol Dellaiera

Câu trả lời:


94

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();

6
Rất, rất thông minh nhưng kludgy trong một dự án thế giới thực IMO! (và +1 để chống lại người phản đối ẩn danh)
Pekka

2
@pekka - nhưng làm thế nào, chính xác, nó có phải là kludgy không? Chắc chắn, tôi không nói rằng tôi là một chuyên gia trong việc mở rộng một đối tượng trong thời gian chạy trong PHP, nhưng thành thật mà nói tôi không thể nói rằng tôi thấy sai nhiều với nó. (có lẽ tôi chỉ có khẩu vị kém)
karim79,

16
Tôi sẽ thêm một kiểm tra is_callable nếu cũng như vậy (Vì vậy, bạn không vô tình cố gắng gọi một chuỗi). Và cũng ném BadMethodCallException () nếu bạn không thể tìm thấy một phương thức, vì vậy bạn không có nó trả về và nghĩ rằng nó đã trả về thành công. Ngoài ra, làm cho các param đầu tiên của hàm đối tượng chính nó, và sau đó làm một 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ó ...
ircmaxell

3
Tôi nghĩ rằng mọi người đang thiếu điểm, yêu cầu ban đầu là sử dụng một đối tượng không có lớp.
thomas-peter

1
Tôi thực sự đề nghị bạn nên loại bỏ hoàn toàn kiểm tra Isset () bởi vì nếu hàm đó không được xác định, bạn có thể không muốn trả về một cách im lặng.
Alexis Wilke,


20

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"

12

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; $thiskhô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ập privatehoặc protectedcá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/ protectedkhả 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.


2

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

1

Để 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.


Tôi giả định (và mã của tôi cũng vậy) rằng bạn đang chạy trên PHP <5.3.0 và do đó cần kludges và hacks để làm việc này.
ivans

Hỏi nếu có ai muốn nhận xét về phiếu giảm giá là vô nghĩa, tôi cho là ...
ivans

Có thể là, có một số người ủng hộ hàng loạt đang diễn ra xung quanh đây. Nhưng có lẽ bạn muốn nói rõ hơn một chút về những gì bạn đã làm? Như bạn thấy, đây là một câu hỏi thú vị chưa có câu trả lời chắc chắn.
Pekka

1

Nếu không có __callgiải pháp, bạn có thể sử dụng bindTo(PHP> = 5.4), để gọi phương thức với $thisràng buộc $menhư 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(); 

0

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 ).


0

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ó privategâ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);

Nếu đó là một bản hack không nên được sử dụng, nó không nên được đăng như một câu trả lời.
miken32
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.