การเรียกการปิดที่กำหนดให้กับคุณสมบัติของวัตถุโดยตรง


109

ฉันต้องการที่จะสามารถเรียกการปิดที่ฉันกำหนดให้กับคุณสมบัติของวัตถุได้โดยตรงโดยไม่ต้องกำหนดการปิดให้กับตัวแปรแล้วเรียกมัน เป็นไปได้หรือไม่

Fatal error: Call to undefined method stdClass::callback()โค้ดข้างล่างนี้ไม่ทำงานและสาเหตุ

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

1
นี่คือสิ่งที่คุณต้องการ: github.com/ptrofimov/jslikeobjectยิ่งไปกว่านั้น: คุณสามารถใช้ $ this ภายในการปิดและใช้การสืบทอด เพียง PHP> = 5.4!
Renato Cuccinotto

คำตอบ:


106

ใน PHP7 คุณสามารถทำได้

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

หรือใช้Closure :: call ()แม้ว่าจะใช้ไม่ได้กับไฟล์StdClass.


ก่อน PHP7 คุณต้องใช้__callเมธอดมายากลเพื่อดักฟังการโทรและเรียกใช้การโทรกลับ (ซึ่งเป็นไปไม่ได้StdClassแน่นอนเพราะคุณไม่สามารถเพิ่ม__callเมธอดได้)

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

โปรดทราบว่าคุณไม่สามารถทำได้

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

ใน__callร่างกายเพราะสิ่งนี้จะทำให้เกิด__callการวนซ้ำแบบไม่สิ้นสุด


2
บางครั้งคุณจะพบว่าไวยากรณ์นี้มีประโยชน์ - call_user_func_array (คุณสมบัติ $ นี้ -> $, $ args); เมื่อเป็นเรื่องเกี่ยวกับคุณสมบัติคลาสที่เรียกได้ไม่ใช่วิธีการ
Nikita Gopkalo

105

คุณสามารถทำได้โดยเรียก __invoke เมื่อปิดเนื่องจากเป็นวิธีการวิเศษที่วัตถุใช้ในการทำงานเหมือนฟังก์ชัน:

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

แน่นอนว่าจะใช้ไม่ได้หากการเรียกกลับเป็นอาร์เรย์หรือสตริง (ซึ่งอาจเป็นการเรียกกลับที่ถูกต้องใน PHP) - สำหรับการปิดและวัตถุอื่น ๆ ที่มีพฤติกรรม __invoke


3
@marcioAlmada น่าเกลียดมากแม้ว่า.
Mahn

1
@ ฉันคิดว่ามันชัดเจนกว่าคำตอบที่ยอมรับ Explicit จะดีกว่าในกรณีนี้ ถ้าคุณสนใจวิธีการแก้ปัญหาที่ "น่ารัก" จริงๆcall_user_func($obj->callback)ก็ไม่เลว
marcio

แต่ใช้call_user_funcงานได้กับสตริงและนั่นก็ไม่สะดวกเสมอไป
Gherman

2
@cerebriform ความหมายมันทำให้รู้สึกไม่ต้องทำเมื่อหนึ่งจะคาดหวังว่ามันจะ$obj->callback->__invoke(); $obj->callback()เป็นเพียงคำถามของความสม่ำเสมอ
Mahn

3
@Mahn: จริงอยู่มันไม่สอดคล้องกัน ความสม่ำเสมอไม่เคยเป็นชุดที่แข็งแกร่งของ PHP มาก่อน :) แต่ฉันคิดว่ามันสมเหตุสมผลเมื่อเราพิจารณาธรรมชาติของวัตถุ: $obj->callback instanceof \Closure.
อธิการ

24

ในPHP 7คุณสามารถทำสิ่งต่อไปนี้:

($obj->callback)();

สามัญสำนึกเช่นนี้ แต่มันเป็นเรื่องเลวร้ายอย่างแท้จริง พลังอันยิ่งใหญ่ของ PHP7!
tfont

10

เนื่องจากPHP 7สามารถเรียกการปิดได้โดยใช้call()วิธีการ:

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

เนื่องจากPHP 7สามารถดำเนินการกับ(...)นิพจน์ตามอำเภอใจได้เช่นกัน (ตามที่Korikulumอธิบาย):

($obj->callback)();

แนวทางอื่น ๆ ของPHP 5ได้แก่ :

  • โดยใช้วิธีมายากล__invoke()(ตามที่Brilliandอธิบาย)

    $obj->callback->__invoke();
  • โดยใช้call_user_func()ฟังก์ชัน

    call_user_func($obj->callback);
  • การใช้ตัวแปรกลางในนิพจน์

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

แต่ละวิธีมีข้อดีของตัวเองและข้อเสีย แต่การแก้ปัญหาที่รุนแรงมากที่สุดและที่ชัดเจนยังคงเป็นหนึ่งที่นำเสนอโดยกอร์ดอน

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

ดูเหมือนว่าจะเป็นไปได้โดยใช้call_user_func().

call_user_func($obj->callback);

ไม่สง่างามแม้ว่า .... สิ่งที่ @Gordon กล่าวอาจเป็นหนทางเดียวที่จะไป


7

ถ้าคุณยืนยันจริงๆ วิธีแก้ปัญหาอื่นคือ:

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

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

$obj['callback']();

แต่นั่นไม่ใช่ไวยากรณ์ที่ดีที่สุด

อย่างไรก็ตาม parser PHP เสมอถือว่าT_OBJECT_OPERATOR, IDENTIFIER, (เป็นวิธีการเรียก ดูเหมือนว่าจะไม่มีวิธีแก้ไขสำหรับการ->ข้ามตารางวิธีการและเข้าถึงแอตทริบิวต์แทน


7

ฉันรู้ว่ามันเก่า แต่ฉันคิดว่า Traits จัดการปัญหานี้ได้ดีถ้าคุณใช้ PHP 5.4+

ขั้นแรกสร้างลักษณะที่ทำให้คุณสมบัติสามารถเรียกใช้ได้:

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

จากนั้นคุณสามารถใช้ลักษณะนั้นในชั้นเรียนของคุณ:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

ตอนนี้คุณสามารถกำหนดคุณสมบัติผ่านฟังก์ชันที่ไม่ระบุชื่อและเรียกใช้โดยตรง:

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

ว้าว :) นี่ดูหรูหรากว่าที่ฉันพยายามทำมาก คำถามเดียวของฉันเหมือนกับองค์ประกอบขั้วต่อก๊อกน้ำร้อน / เย็นของอังกฤษ: ทำไมถึงยังไม่ได้ติดตั้งในตัว ??
dkellner

ขอบคุณ :). มันอาจจะไม่ได้สร้างขึ้นเนื่องจากความไม่ชัดเจนที่สร้างขึ้น ลองนึกภาพในตัวอย่างของฉันว่ามีฟังก์ชันที่เรียกว่า "เพิ่ม" และคุณสมบัติที่เรียกว่า "เพิ่ม" หรือไม่ การมีวงเล็บบอกให้ PHP มองหาฟังก์ชันที่มีชื่อนั้น
SteveK

2

ควรจะเข้าใจว่าการจัดเก็บการปิดในตัวแปรและการเรียกตัวแปรนั้นเร็วขึ้นจริง (อย่างรวดเร็ว) ขึ้นอยู่กับจำนวนการโทรมันค่อนข้างมากด้วย xdebug (การวัดที่แม่นยำมาก) เรากำลังพูดถึง 1,5 (ตัวประกอบโดยใช้ตัวแปรแทนการเรียก __invoke โดยตรงดังนั้นให้เก็บการปิดไว้ในตัวแปรและเรียกมันว่า


1

นี่เป็นอีกทางเลือกหนึ่งตามคำตอบที่ยอมรับ แต่ขยาย stdClass โดยตรง:

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

ตัวอย่างการใช้งาน:

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

คุณน่าจะดีกว่าถ้าใช้call_user_funcหรือ__invokeแม้ว่า


1

อัปเดต:

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

PHP> = 7:

($obj->callback)();

PHP> = 5.4:

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

ลองทำดูไหม มันใช้ไม่ได้ Call to undefined method AnyObject::callback()(คลาส AnyObject มีอยู่แน่นอน)
Kontrollfreak

0

หากคุณใช้ PHP 5.4 หรือสูงกว่าคุณสามารถผูก callable กับขอบเขตของวัตถุของคุณเพื่อเรียกใช้พฤติกรรมที่กำหนดเอง ตัวอย่างเช่นหากคุณต้องตั้งค่าต่อไปนี้ ..

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

และคุณกำลังดำเนินการในชั้นเรียนเช่นนั้น ..

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

คุณสามารถเรียกใช้ตรรกะของคุณเองราวกับว่าคุณกำลังดำเนินการจากภายในขอบเขตของวัตถุของคุณ

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

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

0

ฉันทราบว่าสิ่งนี้ใช้ได้กับ PHP5.5

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

อนุญาตให้หนึ่งสร้างคอลเล็กชันการปิด psuedo-object

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.