Memanggil closure yang ditugaskan ke properti objek secara langsung

109

Saya ingin dapat memanggil closure yang saya tetapkan ke properti objek secara langsung tanpa menetapkan ulang closure ke variabel dan kemudian memanggilnya. Apakah ini mungkin?

Kode di bawah ini tidak berfungsi dan menyebabkan Fatal error: Call to undefined method stdClass::callback().

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();
Kendall Hopkins
sumber
1
Inilah yang Anda butuhkan: github.com/ptrofimov/jslikeobject Bahkan lebih: Anda dapat menggunakan $ this inside closures dan menggunakan inheritance. Hanya PHP> = 5,4!
Renato Cuccinotto

Jawaban:

106

Pada PHP7, Anda dapat melakukannya

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

atau gunakan Closure :: call () , meskipun itu tidak berfungsi pada a StdClass.


Sebelum PHP7, Anda harus menerapkan __callmetode ajaib untuk mencegat panggilan dan memanggil kembali (yang StdClasstentu saja tidak mungkin , karena Anda tidak dapat menambahkan __callmetode)

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

Perhatikan bahwa Anda tidak dapat melakukannya

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

dalam __calltubuh, karena ini akan memicu __calldalam putaran tak terbatas.

Gordon
sumber
2
Kadang-kadang Anda akan menemukan sintaks ini berguna - call_user_func_array ($ this -> $ property, $ args); ketika ini tentang properti kelas yang dapat dipanggil, bukan metode.
Nikita Gopkalo
104

Anda bisa melakukan ini dengan memanggil __invoke pada closure, karena itulah metode ajaib yang digunakan objek untuk berperilaku seperti fungsi:

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

Tentu saja itu tidak akan berfungsi jika callback adalah larik atau string (yang juga bisa menjadi callback valid dalam PHP) - hanya untuk closure dan objek lain dengan perilaku __invoke.

Brilliand
sumber
3
@marcioAlmada sangat jelek.
Mahn
1
@Mahn Saya pikir itu lebih eksplisit daripada jawaban yang diterima. Eksplisit lebih baik dalam kasus ini. Jika Anda benar-benar peduli dengan solusi yang "lucu", call_user_func($obj->callback)tidak seburuk itu.
marcio
Tetapi call_user_funcbekerja dengan string juga dan itu tidak selalu nyaman
Gherman
2
@cerebriform secara semantik tidak masuk akal untuk melakukan apa yang $obj->callback->__invoke();diharapkan $obj->callback(). Ini hanya masalah konsistensi.
Mahn
3
@Mahn: Memang, itu tidak konsisten. Konsistensi tidak pernah benar-benar sesuai dengan PHP. :) Tapi saya pikir itu tidak masuk akal, bila kita menganggap sifat objek: $obj->callback instanceof \Closure.
uskup
24

Mulai PHP 7, Anda dapat melakukan hal berikut:

($obj->callback)();
Korikulum
sumber
Akal sehat seperti itu, namun itu murni badass. Kekuatan hebat PHP7!
tfont
10

Sejak PHP 7 penutupan dapat dipanggil menggunakan call()metode:

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

Karena PHP 7 juga memungkinkan untuk menjalankan operasi pada (...)ekspresi sembarang (seperti yang dijelaskan oleh Korikulum ):

($obj->callback)();

Pendekatan umum PHP 5 lainnya adalah:

  • menggunakan metode ajaib __invoke()(seperti yang dijelaskan oleh Brilliand )

    $obj->callback->__invoke();
  • menggunakan call_user_func()fungsi tersebut

    call_user_func($obj->callback);
  • menggunakan variabel perantara dalam ekspresi

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

Masing-masing cara memiliki pro dan kontranya masing-masing, namun solusi yang paling radikal dan pasti tetap yang dihadirkan oleh Gordon .

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();
Daniele Orlando
sumber
7

Sepertinya bisa digunakan call_user_func().

call_user_func($obj->callback);

tidak elegan, meskipun .... Apa yang dikatakan @Gordon mungkin satu-satunya cara untuk pergi.

Pekka
sumber
7

Nah, jika Anda benar-benar bersikeras. Solusi lain adalah:

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

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

$obj['callback']();

Tapi itu bukan sintaks terbaik.

Namun, PHP parser selalu memperlakukan T_OBJECT_OPERATOR, IDENTIFIER, (sebagai metode panggilan. Tampaknya tidak ada solusi untuk membuat ->bypass tabel metode dan mengakses atribut sebagai gantinya.

mario
sumber
7

Saya tahu ini sudah tua, tapi saya pikir Sifat menangani masalah ini dengan baik jika Anda menggunakan PHP 5.4+

Pertama, buat sifat yang membuat properti bisa dipanggil:

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

Kemudian, Anda dapat menggunakan sifat itu di kelas Anda:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Sekarang, Anda dapat mendefinisikan properti melalui fungsi anonim dan memanggilnya secara langsung:

$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
SteveK
sumber
Wow :) Ini jauh lebih elegan dari apa yang saya coba lakukan. Satu-satunya pertanyaan saya adalah sama dengan elemen konektor keran panas / dingin Inggris: KENAPA belum terpasang ??
dkellner
Terima kasih :). Itu mungkin tidak dibangun karena ambiguitas yang diciptakannya. Bayangkan dalam contoh saya, jika sebenarnya ada fungsi bernama "add" dan properti bernama "add". Kehadiran tanda kurung memberitahu PHP untuk mencari fungsi dengan nama itu.
SteveK
2

baik, harus ditekankan bahwa menyimpan closure dalam sebuah variabel, dan memanggil varible sebenarnya (anehnya) lebih cepat, tergantung pada jumlah panggilan, itu menjadi cukup banyak, dengan xdebug (pengukuran yang sangat tepat), kita sedang membicarakan 1,5 (faktor, dengan menggunakan varible, alih-alih langsung memanggil __invoke. Jadi sebagai gantinya, cukup simpan closure dalam varible dan panggil.

Kmtdk
sumber
2

Diperbarui:

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

PHP> = 7:

($obj->callback)();

PHP> = 5,4:

$callback = $obj->callback;  
$callback();
M Rostami
sumber
Apakah kamu sudah mencobanya? Itu tidak berhasil. Call to undefined method AnyObject::callback()(Kelas AnyObject ada, tentu saja.)
Kontrollfreak
1

Berikut alternatif lain berdasarkan jawaban yang diterima tetapi memperluas stdClass secara langsung:

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

Contoh penggunaan:

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

Anda mungkin lebih baik menggunakan call_user_funcatau __invokemeskipun.

Mahn
sumber
0

Jika Anda menggunakan PHP 5.4 atau yang lebih baru, Anda dapat mengikat callable ke cakupan objek Anda untuk menjalankan perilaku kustom. Jadi misalnya jika Anda memiliki pengaturan berikut ..

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

Dan Anda beroperasi di kelas seperti itu ..

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

Anda bisa menjalankan logika Anda sendiri seolah-olah Anda beroperasi dari dalam lingkup objek Anda

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

echo $foo->getValue(); // prints "something else"
Lee Davis
sumber
0

Saya perhatikan bahwa ini berfungsi di PHP5.5

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

Mengizinkan seseorang untuk membuat kumpulan objek psuedo dari penutupan.

Gordon Rouse
sumber