الفحص الذي يكون صحيحًا دائمًا
توجد 23 عبارة UPDATE في هذه الخدمة. واحدة منها فقط تتحقق مما إذا كانت قد غيّرت شيئًا. وهذا الفحص هو:
'success' => $n->rowCount() >= 0
rowCount() تعيد 0 حين لا يطابق أي صف. والصفر أكبر من الصفر أو يساويه. الفحص ينجح مهما حدث.
ما الغرض من هذه العبارة
تحفظ مربع اختيار: هل يريد صاحب العداد ملخصًا أسبوعيًا بالبريد الإلكتروني. العنوان محفوظ في جدول منفصل، ولا يظهر فيه صف إلا بعد أن ينقر أحدهم رابط التأكيد في رسالة طلبها بنفسه. أربعة عدادات فقط لديها صف كهذا. وعدد العدادات النشطة 2,218.
إذن في 99.8% من العدادات لا تطابق عبارة UPDATE أي شيء. وتجيب نقطة النهاية بـ success: true ولا يُحفظ الإعداد، إذ لا يوجد مكان يُحفظ فيه.
التعليق فوقها صحيح
قبلها بثلاثة أسطر، في الدالة نفسها:
«فقط حيث يوجد مسار رد مؤكَّد. من دونه لا يوجد عنوان يمكن أن تُرسل إليه الرسالة — وسيكون مربع الاختيار قد وعد بشيء لا يحدث.»
وهذا صحيح تمامًا. أحدهم فكّر في هذه الحالة، وفهمها، ودوّن الاستدلال. ثم استخدم السطر الذي تحته >= حيث كان الاستدلال يقتضي >.
وهذا يستحق النظر إليه مباشرة، لأن التفسير المعتاد — لم يفكر فيه أحد — متاح وخاطئ. التفكير موجود في الملف. ما أخفق هو حرف واحد، في موضع تبدو فيه النسخة الخاطئة والنسخة الصحيحة متطابقتين للوهلة الأولى، وتتصرفان بالطريقة نفسها في كل اختبار يوجد فيه صف لتحديثه.
لا أحد يُخدَع
وهنا الجزء الذي يسهل إغفاله، وإغفاله سيجعل هذا المقال أكثر إثارة وأقل صدقًا.
لا تعرض الصفحة مربع الاختيار ذاك إلا إذا كان الصف موجودًا. على بُعد شاشة واحدة، في الكود الذي يرسم مربع إعدادات صاحب العداد، يُستعلم عن الجدول نفسه أولًا، ويُتخطّى الجزء كله إذا عاد الاستعلام فارغًا. وهكذا فإن صاحب العداد الذي ليس لديه عنوان مؤكَّد لا يرى عنصر التحكم أبدًا، ولا ينقره، ولا يتلقى التأكيد الكاذب.
لا يمكن الوصول إلى الفحص المعطوب إلا باستدعاء نقطة النهاية مباشرة برمز صالح. ومن يفعل ذلك يتلقى success: true دون أن يُحفظ أي إعداد. هذا عيب حقيقي لكنه محدود.
لماذا يستحق التدوين مع ذلك
الميزة آمنة بفضل حارس لم يدوّنه أحد على أنه الحارس. السطر الموجود لجعلها آمنة لا يجعلها آمنة. ما يفعل ذلك هو شرط عرض في دالة أخرى، يشرح تعليقه سبب إخفاء مربع الاختيار — لا أن شيئًا ما يعتمد على بقائه مخفيًا.
احذف شرط العرض ذاك أو أعد هيكلته — وهو أمر معقول عند إعادة تصميم لوحة إعدادات — فيظهر العيب فورًا، دون أن يربط أي شيء في أي مكان بين التغييرين. الأمان حقيقي، لكنه عرضي، والأمان العرضي من النوع الذي يختفي أثناء عمل لا علاقة له به.
والكود نفسه يفعل ذلك بشكل صحيح في الملف المجاور. فالدالة المقابلة في حلقة المواقع تسأل أولًا إن كان الصف موجودًا، وتعيد false إن لم يكن، ولا تُصدر التحديث إطلاقًا. ولا توجد في ذلك الجدول حاليًا أي صفوف، لذا تعيد تلك الدالة false في كل مرة تُستدعى فيها، وهذه هي الإجابة الصحيحة.
الشكل العام
عبارة UPDATE لا تطابق أي صف ليست خطأً في أي قاعدة بيانات. إنها عبارة ناجحة لم تفعل شيئًا، وكل طبقة فوقها ستبلّغ عن النجاح ما لم يسأل شيء ما. اثنتان وعشرون من العبارات هنا لا تسأل، وهذا لا بأس به في معظمها: فهي تحدّث صفًا أثبت الطلب وجوده مسبقًا.
والعبارة الوحيدة التي كان عليها أن تسأل، سألت بطريقة لا يمكن أن تفشل. rowCount() >= 0 ليس فحصًا ضعيفًا، بل غياب فحص يرتدي زيّ فحص — وهو أسوأ من عدم وجود فحص، لأن الشخص التالي الذي يقرأ الدالة يرى نتيجة تُفحص فيتوقف عن البحث.
التعبير rowCount() > 0 لا يرد ولا مرة واحدة في هذا الكود. هذا هو الرقم الذي حوّل خطأً مطبعيًا من حرف واحد إلى شيء يستحق مقالًا: ليس لأنه كُتب خطأً مرة، بل لأنه لم تكن هناك نسخة صحيحة منه في أي مكان لمقارنته بها.
أُصلح في 28 أغسطس 2026. صارت نقطة النهاية تسأل الآن إن كان الصف موجودًا قبل التحديث، كما كانت دالة حلقة المواقع في الملف المجاور تفعل من قبل، وتجيب بـ false إن لم يكن موجودًا. أما الإصلاح البديهي بحرف واحد — المقارنة بأكبر من صفر بدلًا من ذلك — فقد قيس أولًا ثم رُفض: فبرنامج تشغيل قاعدة البيانات يبلّغ عن الصفوف المتغيّرة لا الصفوف المطابقة، لذا فإن ضبط مربع الاختيار على القيمة التي يحملها أصلًا لا يغيّر شيئًا، وكانت تلك النسخة ستبلّغ عن فشل عملية سليمة. كلتا النسختين خاطئة، في اتجاهين متعاكسين.