
Mungkin, seseorang harus mulai dengan fakta bahwa fungsi anonim (penutupan) dalam PHP bukan fungsi, tetapi objek dari kelas
Penutupan . Sebenarnya, artikel ini bisa saja selesai, tetapi jika ada yang tertarik dengan detailnya, selamat datang ke kucing.
Agar tidak berdasar:
$func = function (){}; var_dump($func); --------- object(Closure)
Ke depan, saya akan mengatakan bahwa ini bukan objek biasa. Mari kita cari tahu.
Misalnya, kode seperti itu
$func = function (){ echo 'Hello world!'; }; $func();
mengkompilasi ke dalam sekumpulan opcode:
line
Blok dengan deskripsi fungsi tubuh tidak terlalu menarik bagi kami, tetapi di blok pertama ada dua opcode yang menarik:
DECLARE_LAMBDA_FUNCTION dan
INIT_DYNAMIC_CALL . Mari kita mulai dengan yang kedua.
INIT_DYNAMIC_CALL
Opcode ini digunakan ketika kompiler melihat panggilan fungsi pada variabel atau array. Yaitu
$variable(); ['ClassName', 'staticMethod']();
Ini bukan opcode unik khusus untuk penutupan saja. Sintaks ini juga berfungsi untuk objek dengan memanggil metode
__invoke () , untuk variabel string yang berisi nama fungsi (
$ a = 'funcName'; $ a (); ), dan untuk array yang berisi nama kelas dan metode statis di dalamnya.
Dalam kasus penutupan, kami tertarik untuk memanggil variabel dengan objek, yang logis.
Menuju lebih dalam ke kode VM yang memproses opcode ini, kita menuju ke fungsi
zend_init_dynamic_call_object , di mana kita akan melihat yang berikut (mengiris):
zend_execute_data *zend_init_dynamic_call_object(zend_object *function, uint32_t num_args) { zend_function *fbc; zend_class_entry *called_scope; zend_object *object; ... if (EXPECTED(function->handlers->get_closure) && EXPECTED(function->handlers->get_closure(function, &called_scope, &fbc, &object) == SUCCESS)) { ... } else { zend_throw_error(NULL, "Function name must be a string"); return NULL; } ... }
Sangat lucu bahwa panggilan metode __invoke yang biasa dalam hal VM adalah upaya untuk memanggil penutupan - get_closure .Sebenarnya, pada titik ini perbedaan dimulai dalam menangani panggilan ke fungsi anonim dan metode
__invoke objek biasa.
Dalam PHP, setiap objek memiliki satu set penangan yang berbeda yang mendefinisikan utilitas dan metode sihirnya.
Perangkat standar terlihat seperti ini ZEND_API const zend_object_handlers std_object_handlers = { 0, zend_object_std_dtor, zend_objects_destroy_object, zend_objects_clone_obj, zend_std_read_property, zend_std_write_property, zend_std_read_dimension, zend_std_write_dimension, zend_std_get_property_ptr_ptr, NULL, NULL, zend_std_has_property, zend_std_unset_property, zend_std_has_dimension, zend_std_unset_dimension, zend_std_get_properties, zend_std_get_method, zend_std_get_constructor, zend_std_get_class_name, zend_std_compare_objects, zend_std_cast_object_tostring, NULL, zend_std_get_debug_info, zend_std_get_closure, zend_std_get_gc, NULL, NULL, NULL, };
Sekarang kami tertarik dengan penangan
get_closure . Untuk objek biasa, ini menunjuk ke fungsi
zend_std_get_closure , yang memeriksa bahwa fungsi
__invoke didefinisikan untuk objek dan mengembalikan pointer ke objek tersebut atau kesalahan. Tetapi untuk kelas
Penutupan , yang mengimplementasikan fungsi anonim, dalam susunan penangan ini hampir semua fungsi utilitas didefinisikan ulang, termasuk yang mengontrol siklus hidup. Yaitu Meskipun bagi pengguna itu terlihat seperti objek biasa, tetapi sebenarnya itu adalah mutan dengan kekuatan super :)
Daftarkan penangan untuk objek Penutupan kelas void zend_register_closure_ce(void) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Closure", closure_functions); zend_ce_closure = zend_register_internal_class(&ce); zend_ce_closure->ce_flags |= ZEND_ACC_FINAL; zend_ce_closure->create_object = zend_closure_new; zend_ce_closure->serialize = zend_class_serialize_deny; zend_ce_closure->unserialize = zend_class_unserialize_deny; memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers)); closure_handlers.free_obj = zend_closure_free_storage; closure_handlers.get_constructor = zend_closure_get_constructor; closure_handlers.get_method = zend_closure_get_method; closure_handlers.write_property = zend_closure_write_property; closure_handlers.read_property = zend_closure_read_property; closure_handlers.get_property_ptr_ptr = zend_closure_get_property_ptr_ptr; closure_handlers.has_property = zend_closure_has_property; closure_handlers.unset_property = zend_closure_unset_property; closure_handlers.compare_objects = zend_closure_compare_objects; closure_handlers.clone_obj = zend_closure_clone; closure_handlers.get_debug_info = zend_closure_get_debug_info; closure_handlers.get_closure = zend_closure_get_closure; closure_handlers.get_gc = zend_closure_get_gc; }
Manual mengatakan:
Selain metode yang dijelaskan di sini, kelas ini juga memiliki metode __invoke . Metode ini hanya diperlukan untuk kompatibilitas dengan kelas lain di mana panggilan ajaib diterapkan, karena metode ini tidak digunakan saat memanggil fungsi.
Dan ini benar. Fungsi
get_closure untuk penutupan tidak mengembalikan
__invoke , tetapi fungsi Anda dari mana penutupan dibuat.
Anda dapat mempelajari sendiri sumbernya dengan lebih detail - file
zend_closure.c , dan kami akan beralih ke opcode berikutnya.
DECLARE_LAMBDA_FUNCTION
Tapi ini opcode yang khusus untuk sirkuit dan tidak lagi bekerja dengan apa pun. Di bawah kap prosesor, ada tiga operasi utama:
- Pointer ke fungsi yang dikompilasi dicari, yang akan menjadi inti dari penutupan.
- Konteks untuk membuat penutupan (dengan kata lain, ini ) didefinisikan.
- Berdasarkan dua poin pertama, objek kelas Penutupan dibuat .
Dan di sini di tempat ini berita yang tidak menyenangkan dimulai.
Jadi apa yang salah dengan fungsi anonim?
Membuat penutupan adalah operasi yang lebih sulit daripada menciptakan objek biasa. Tidak hanya mekanisme standar untuk membuat objek yang disebut, itu juga menambahkan sejumlah logika, yang paling tidak menyenangkan adalah menyalin seluruh array opcodes fungsi Anda ke tubuh penutupan. Ini sendiri tidak begitu menakutkan, tetapi tepat sampai Anda mulai menggunakannya "salah".
Untuk memahami dengan tepat di mana masalah menunggu, kami akan menganalisis kasus ketika penutupan dibuat.
Penutupan dibuat kembali:
a) pada setiap pemrosesan
opcode DECLARE_LAMBDA_FUNCTION.
Secara intuitif - persisnya kasus di mana penutupan terlihat bagus, tetapi sebenarnya objek penutupan baru akan dibuat pada setiap iterasi dari loop.
foreach($values as $value){ doSomeStuff($value, function($args) { closureBody }); }
b) setiap kali metode
bind dan
bindTo dipanggil :
Di sini penutupan akan dibuat kembali juga di setiap iterasi.
$closure = function($args) { closureBody }; foreach($objects as $object){ $closure->bindTo($object); $object->doSomeStuff($closure); }
c) setiap kali metode
panggilan dipanggil , jika generator digunakan sebagai fungsi. Dan jika bukan generator, tetapi fungsi biasa, maka hanya bagian dengan menyalin array opcode yang dieksekusi. Hal-hal seperti itu.
Kesimpulan
Jika kinerja tidak penting bagi Anda di semua biaya, maka fungsi anonim nyaman dan menyenangkan. Dan jika penting, maka mungkin tidak sepadan.
Bagaimanapun, sekarang Anda tahu bahwa penutupan dan siklus, jika mereka tidak disiapkan dengan benar, adalah kombinasi seperti itu.
Terima kasih atas perhatian anda!