
ربما ، ينبغي للمرء أن يبدأ بحقيقة أن وظيفة مجهولة الهوية (إغلاق) في PHP ليست وظيفة ، ولكن كائن من فئة
الإغلاق . في الواقع ، كان من الممكن إكمال هذه المقالة ، ولكن إذا كان أي شخص مهتمًا بالتفاصيل ، فمرحبًا بك في cat.
لكي لا تكون بلا أساس:
$func = function (){}; var_dump($func); --------- object(Closure)
بالنظر إلى المستقبل ، سأقول أن هذا ليس شيئًا عاديًا بالفعل. دعونا معرفة ذلك.
على سبيل المثال ، مثل هذا الرمز
$func = function (){ echo 'Hello world!'; }; $func();
يجمع في مثل هذه المجموعة من شفرة التشغيل:
line
لا تعتبر المجموعة التي تحتوي على وصف
لنشاط الوظيفة مثيرة للاهتمام بشكل خاص ، ولكن في المجموعة الأولى يوجد
كودان تشغيليان مثيران للاهتمام:
DECLARE_LAMBDA_FUNCTION و
INIT_DYNAMIC_CALL . لنبدأ مع الثانية.
INIT_DYNAMIC_CALL
يتم استخدام شفرة التشغيل هذه عندما يرى المترجم استدعاء دالة على متغير أو صفيف. أي
$variable(); ['ClassName', 'staticMethod']();
هذه ليست شفرة تشغيل فريدة خاصة بالإغلاق فقط. يعمل بناء الجملة هذا أيضًا مع الكائنات عن طريق استدعاء الأسلوب
__invoke () ، ومتغيرات السلسلة التي تحتوي على اسم الوظيفة (
$ a = 'funcName'؛ $ a ()؛ ) ،
وللصفائف التي تحتوي على اسم الفئة والأسلوب الساكن فيها.
في حالة الإغلاق ، نحن مهتمون باستدعاء متغير ذي كائن ، وهذا منطقي.
بالانتقال إلى رمز VM الذي يعالج شفرة التشغيل هذه ، نصل إلى وظيفة
zend_init_dynamic_call_object ، حيث سنرى (التقطيع) التالي:
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; } ... }
من المضحك أن طريقة مألوفة __invoke فيما يتعلق بـ VM هي محاولة لإغلاق الاتصال - get_closure .في الواقع ، في هذه المرحلة ، يبدأ الفرق في معالجة الاستدعاء للدالة المجهولة وطريقة
__invoke لكائن عادي.
في PHP ، يحتوي كل كائن على مجموعة من معالجات مختلفة تحدد فائدته وأساليبه السحرية.
مجموعة قياسية تبدو مثل هذا 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, };
نحن مهتمون الآن
بمعالج get_closure . بالنسبة للكائن العادي ، فإنه يشير إلى الدالة
zend_std_get_closure ، والتي تتحقق من أن الدالة
__invoke محددة للكائن وتُرجع إما مؤشرًا إليها أو خطأً. ولكن بالنسبة لفئة
الإغلاق ، التي تنفذ وظائف مجهولة الهوية ، في هذه المجموعة من المعالجات ، يتم إعادة تعريف جميع وظائف الأداة المساعدة تقريبًا ، بما في ذلك تلك التي تتحكم في دورة الحياة. أي على الرغم من أنه يبدو للمستخدم كأنه كائن عادي ، إلا أنه في الحقيقة متحولة مع القوى العظمى :)
تسجيل معالجات لكائن فئة إغلاق 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; }
دليل يقول:
بالإضافة إلى الطرق الموضحة هنا ، تحتوي هذه الفئة أيضًا على طريقة __invoke . هذه الطريقة ضرورية فقط للتوافق مع الفئات الأخرى التي يتم فيها تنفيذ المكالمة السحرية ، حيث لا يتم استخدام هذه الطريقة عند استدعاء الوظيفة.
وهذا صحيح. لا
تُرجع الدالة
get_closure للإغلاق
__invoke ، لكن
وظيفتك التي تم إنشاء الإغلاق منها.
يمكنك دراسة المصادر بمزيد من التفصيل بنفسك - الملف
zend_closure.c ، وسوف ننتقل إلى شفرة التشغيل التالية.
DECLARE_LAMBDA_FUNCTION
ولكن هذا شفرة تشغيل مخصصة للدارات ولم تعد تعمل مع أي شيء. تحت غطاء المعالج ، هناك ثلاث عمليات رئيسية:
- يتم طلب مؤشر دالة مترجمة ، والتي ستكون جوهر الإغلاق.
- يتم تعريف سياق إنشاء الإغلاق (بمعنى آخر ، هذا ).
- بناءً على النقطتين الأوليين ، يتم إنشاء كائن من فئة الإغلاق .
وهنا في هذا المكان لا تبدأ أخبار سارة للغاية.
فما الخطأ في وظائف مجهولة؟
إنشاء إغلاق عملية أكثر صعوبة من إنشاء كائن عادي. لا يقتصر الأمر على الآلية القياسية لإنشاء كائن يسمى ، بل يضيف أيضًا قدرًا معينًا من المنطق ، وأغلبها غير المرغوب فيه هو نسخ مجموعة كاملة من أكواد شفرة الوظيفة الخاصة بك إلى نص الإغلاق. هذا في حد ذاته ليس مخيفًا جدًا ، ولكن بالضبط حتى تبدأ في استخدامه "بشكل غير صحيح".
لفهم بالضبط أين تنتظر المشكلات ، سنقوم بتحليل الحالات عند إنشاء إغلاق.
يتم إعادة إنشاء الإغلاق:
أ) في كل معالجة
شفرة التشغيل DECLARE_LAMBDA_FUNCTION.
حدسي - بالضبط الحالة التي يبدو فيها الإغلاق جيدًا ، ولكن في الحقيقة سيتم إنشاء كائن إغلاق جديد عند كل تكرار للحلقة.
foreach($values as $value){ doSomeStuff($value, function($args) { closureBody }); }
ب) في كل مرة
تسمى أساليب
الربط والربط :
هنا سيتم إعادة إنشاء الإغلاق أيضًا عند كل تكرار.
$closure = function($args) { closureBody }; foreach($objects as $object){ $closure->bindTo($object); $object->doSomeStuff($closure); }
ج) في كل مرة يتم
استدعاء طريقة
النداء ، إذا تم استخدام مولد كدالة. وإذا لم يكن مولد ، ولكن وظيفة عادية ، ثم يتم تنفيذ الجزء فقط مع نسخ مجموعة من رموز شفرة. مثل هذه الأشياء.
النتائج
إذا كان الأداء غير مهم بالنسبة لك بأي ثمن ، فإن الوظائف المجهولة مريحة وممتعة. وإذا كان ذلك مهمًا ، فربما لا يستحق كل هذا العناء.
في أي حال ، أنت تعرف الآن أن عمليات الإغلاق والدورات ، إذا لم يتم إعدادها بشكل صحيح ، هي مزيج من هذا القبيل.
شكرا لاهتمامكم!