Gọi lệnh đóng được chỉ định trực tiếp cho thuộc tính đối tượng


109

Tôi muốn có thể gọi một bao đóng mà tôi chỉ định trực tiếp cho thuộc tính của một đối tượng mà không cần gán lại bao đóng cho một biến và sau đó gọi nó. Điều này có khả thi không?

Mã bên dưới không hoạt động và nguyên nhân Fatal error: Call to undefined method stdClass::callback().

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();

1
Đây chính xác là những gì bạn cần: github.com/ptrofimov/jslikeobject Thậm chí nhiều hơn: bạn có thể sử dụng $ this bên trong các bao đóng và sử dụng kế thừa. Chỉ PHP> = 5,4!
Renato Cuccinotto

Câu trả lời:


106

Đối với PHP7, bạn có thể làm

$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');

hoặc sử dụng Closure :: call () , mặc dù điều đó không hoạt động trên a StdClass.


Trước PHP7, bạn phải triển khai __callphương thức ma thuật để chặn cuộc gọi và gọi lại (tất nhiên là không thể thực hiện được StdClass, vì bạn không thể thêm __callphương thức)

class Foo
{
    public function __call($method, $args)
    {
        if(is_callable(array($this, $method))) {
            return call_user_func_array($this->$method, $args);
        }
        // else throw exception
    }
}

$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');

Lưu ý rằng bạn không thể làm

return call_user_func_array(array($this, $method), $args);

trong __callcơ thể, bởi vì điều này sẽ kích hoạt __calltrong một vòng lặp vô hạn.


2
Đôi khi bạn sẽ thấy cú pháp này hữu dụng - call_user_func_array ($ this -> $ property, $ args); khi đó là thuộc tính lớp có thể gọi, không phải là một phương thức.
Nikita Gopkalo

104

Bạn có thể làm điều này bằng cách gọi __invoke khi đóng, vì đó là phương thức ma thuật mà các đối tượng sử dụng để hoạt động giống như các hàm:

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback->__invoke();

Tất nhiên điều đó sẽ không hoạt động nếu lệnh gọi lại là một mảng hoặc một chuỗi (cũng có thể là các lệnh gọi lại hợp lệ trong PHP) - chỉ dành cho các bao đóng và các đối tượng khác có hành vi __invoke.


3
@marcioAlmada mặc dù rất xấu xí.
Mahn

1
@Mahn Tôi nghĩ nó rõ ràng hơn câu trả lời được chấp nhận. Rõ ràng là tốt hơn trong trường hợp này. Nếu bạn thực sự quan tâm đến một giải pháp "dễ thương", call_user_func($obj->callback)không phải là xấu.
marcio

Nhưng cũng call_user_funchoạt động với các chuỗi và điều đó không phải lúc nào cũng thuận lợi
Gherman

2
@cerebriform về mặt ngữ nghĩa, không có ý nghĩa gì $obj->callback->__invoke();khi phải làm khi người ta mong đợi $obj->callback(). Nó chỉ là một câu hỏi về tính nhất quán.
Mahn

3
@Mahn: Được, nó không nhất quán. Mặc dù vậy, tính nhất quán chưa bao giờ thực sự là điểm mạnh của PHP. :) Nhưng tôi nghĩ rằng nó không có ý nghĩa, khi người ta xem xét các đối tượng tự nhiên: $obj->callback instanceof \Closure.
giám mục

24

Đối với PHP 7, bạn có thể làm như sau:

($obj->callback)();

Cảm giác bình thường như vậy, nhưng nó hoàn toàn là badass. Sức mạnh tuyệt vời của PHP7!
tfont

10

PHP 7, một bao đóng có thể được gọi bằng call()phương thức:

$obj->callback->call($obj);

PHP 7 cũng có thể thực thi các hoạt động trên các (...)biểu thức tùy ý (như Korikulum giải thích ):

($obj->callback)();

Các cách tiếp cận PHP 5 phổ biến khác là:

  • sử dụng phương pháp ma thuật __invoke()(theo giải thích của Brilliand )

    $obj->callback->__invoke();
  • sử dụng call_user_func()chức năng

    call_user_func($obj->callback);
  • sử dụng một biến trung gian trong một biểu thức

    ($_ = $obj->callback) && $_();

Mỗi cách đều có ưu và nhược điểm riêng, nhưng giải pháp triệt để và triệt để nhất vẫn là giải pháp do Gordon đưa ra .

class stdKlass
{
    public function __call($method, $arguments)
    {
        // is_callable([$this, $method])
        //   returns always true when __call() is defined.

        // is_callable($this->$method)
        //   triggers a "PHP Notice: Undefined property" in case of missing property.

        if (isset($this->$method) && is_callable($this->$method)) {
            return call_user_func($this->$method, ...$arguments);
        }

        // throw exception
    }
}

$obj = new stdKlass();
$obj->callback = function() { print "HelloWorld!"; };
$obj->callback();

7

Nó dường như có thể sử dụng call_user_func().

call_user_func($obj->callback);

không thanh lịch, mặc dù .... Những gì @Gordon nói có lẽ là cách duy nhất để đi.


7

Vâng, nếu bạn thực sự nhấn mạnh. Một giải pháp khác sẽ là:

$obj = new ArrayObject(array(),2);

$obj->callback = function() {
    print "HelloWorld!";
};

$obj['callback']();

Nhưng đó không phải là cú pháp đẹp nhất.

Tuy nhiên, phân tích cú pháp PHP luôn luôn đối xử T_OBJECT_OPERATOR, IDENTIFIER, (như lời gọi phương thức. Dường như không có cách giải quyết nào để ->bỏ qua bảng phương thức và thay vào đó truy cập vào các thuộc tính.


7

Tôi biết điều này đã cũ, nhưng tôi nghĩ rằng Traits xử lý vấn đề này một cách hiệu quả nếu bạn đang sử dụng PHP 5.4+

Đầu tiên, hãy tạo một đặc điểm giúp các thuộc tính có thể gọi được:

trait CallableProperty {
    public function __call($method, $args) {
        if (property_exists($this, $method) && is_callable($this->$method)) {
            return call_user_func_array($this->$method, $args);
        }
    }
}

Sau đó, bạn có thể sử dụng đặc điểm đó trong các lớp của mình:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Bây giờ, bạn có thể xác định thuộc tính thông qua các hàm ẩn danh và gọi chúng trực tiếp:

$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4

Wow :) Điều này thanh lịch hơn nhiều so với những gì tôi đã cố gắng làm. Câu hỏi duy nhất của tôi cũng giống như đối với các yếu tố kết nối vòi nóng / lạnh của Anh: TẠI SAO nó chưa được tích hợp sẵn ??
dkellner

Cảm ơn :). Nó có thể không được tích hợp vì sự mơ hồ mà nó tạo ra. Hãy tưởng tượng trong ví dụ của tôi, nếu thực sự có một chức năng được gọi là "thêm" và một thuộc tính được gọi là "thêm". Sự hiện diện của dấu ngoặc đơn yêu cầu PHP tìm kiếm một hàm có tên đó.
SteveK

2

tốt, nó nên được trao quyền rằng việc lưu trữ bao đóng trong một biến và gọi biến thực sự nhanh hơn (kỳ lạ), tùy thuộc vào số lượng cuộc gọi, nó trở nên khá nhiều, với xdebug (đo lường rất chính xác), chúng ta đang nói về 1,5 (thừa số, bằng cách sử dụng một biến thể, thay vì gọi trực tiếp __invoke. Vì vậy, thay vào đó, chỉ cần lưu trữ bao đóng trong một biến thể và gọi nó.


2

Đã cập nhật:

$obj = new stdClass();
$obj->callback = function() {
     print "HelloWorld!";
};

PHP> = 7:

($obj->callback)();

PHP> = 5,4:

$callback = $obj->callback;  
$callback();

Bạn đã thử cái này chưa? Nó không hoạt động. Call to undefined method AnyObject::callback()(Tất nhiên là có Class AnyObject.)
Kontrollfreak

1

Đây là một giải pháp thay thế khác dựa trên câu trả lời được chấp nhận nhưng mở rộng trực tiếp stdClass:

class stdClassExt extends stdClass {
    public function __call($method, $args)
    {
        if (isset($this->$method)) {
            $func = $this->$method;
            return call_user_func_array($func, $args);
        }
    }
}

Ví dụ sử dụng:

$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();

Bạn có lẽ tốt hơn nên sử dụng call_user_funchoặc __invokemặc dù.


0

Nếu bạn đang sử dụng PHP 5.4 trở lên, bạn có thể liên kết một có thể gọi với phạm vi đối tượng của bạn để gọi hành vi tùy chỉnh. Vì vậy, ví dụ: nếu bạn đã thiết lập sau ..

function run_method($object, Closure $method)
{
    $prop = uniqid();
    $object->$prop = \Closure::bind($method, $object, $object);
    $object->$prop->__invoke();
    unset($object->$prop);
}

Và bạn đã hoạt động trên một lớp học như vậy ..

class Foo
{
    private $value;
    public function getValue()
    {
        return $this->value;
    }
}

Bạn có thể chạy logic của riêng mình như thể bạn đang vận hành từ trong phạm vi đối tượng của mình

$foo = new Foo();
run_method($foo, function(){
    $this->value = 'something else';
});

echo $foo->getValue(); // prints "something else"

0

Tôi lưu ý rằng điều này hoạt động trong PHP5.5

$a = array();
$a['callback'] = function() {
    print "HelloWorld!";
};
$a['callback']();

Cho phép một người tạo một bộ sưu tập bao đóng đối tượng psuedo.

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.