تخطَّ إلى المحتوى

الحذف الناعم ليس حذفًا، وتلك هي المشكلة

الـ trait سطر واحد ويغيّر معنى كل قيد تفرّد وكل مفتاح أجنبي وكل طلب محو في التطبيق. وأغلب الشيفرات تتبنّاه دون أن تقرر أيًّا من ذلك.

قراءة 3 دقيقة

إضافة SoftDeletes إلى نموذج تكلّف سطرًا واحدًا، ويقدّمها كل درس على أنها تراجع مجاني. وهي ليست مجانية. إنها تغيّر معنى الحذف في التطبيق كله، والعواقب تهبط في أماكن لا صلة لها بالنموذج الذي أضفتها إليه.

ماذا يحدث فعلًا

الدالة delete() تكفّ عن الحذف. تضبط deleted_at ويبقى الصف. ونطاق عام يخفيه عن الاستعلامات. تلك هي الآلية كلها، وكل مشكلة أدناه تتبع من بقاء الصف هناك.

قيود التفرّد تكفّ عن العمل

يسجّل مستخدم بعنوان، ثم يحذف حسابه، ثم يحاول التسجيل مجددًا بالعنوان نفسه. الصف لا يزال في الجدول، والفهرس الفريد لا يزال على العمود، ويفشل التسجيل بخطأ قاعدة بيانات عن سجل لا يراه المستخدم ولا يجده الدعم.

يقول التطبيق إن الحساب غير موجود. وتقول قاعدة البيانات إنه موجود. وكلاهما يقول الحقيقة عن شيء مختلف.

مخرجان، وهما خياران لا إصلاحان:

// صف حي واحد، وأي عدد من المحذوفة
$table->unique(['email', 'deleted_at']);

أو حرّر القيمة عند الحذف بإعادة كتابتها:

public function delete(): bool
{
    $this->email = "{$this->email}#deleted-{$this->id}";
    $this->save();
 
    return parent::delete();
}

الأول يحفظ القيمة الأصلية ولا يستطيع فرض التفرّد بين الصفوف الحية والمحذوفة معًا. والثاني يحرّر العنوان ويفقد الأصل - وذلك يهم إن احتجت يومًا أن تعرف لمن كان ذلك الحساب. لا أحدهما خطأ؛ لكن عدم اختيار أيّهما خطأ.

العلاقات تعود بمظهر خاطئ

الأب المحذوف ناعمًا لا يزال يحقق مفاتيحه الأجنبية، لأن الصف موجود. فلا يصير صف الابن يتيمًا في قاعدة البيانات، وضمٌّ لا يعلم بالحذف الناعم سيعيد بكل ارتياح الأبَ المحذوف.

نطاق Eloquent يخفيه حين تستعلم عن النموذج. أما الاستعلامات الخام والتقارير المكتوبة بـ SQL والاستعلامات التجميعية المبنية يدويًا فلا - وهناك تكفّ الأرقام عن التطابق بين شاشتين تعرضان الشيء نفسه.

والأسوأ أن withTrashed() على جانب من العلاقة دون الآخر ينتج نتيجة ليست صحيحة في أي اتجاه.

المحو ليس حذفًا

يمارس شخص حقه في محو بياناته. فيستدعي التطبيق delete(). ويظل الصف هناك، ويظل مقروءًا لكل من يملك وصولًا إلى قاعدة البيانات، ويظل في كل نسخة احتياطية.

لم يُمحَ شيء. لم يُستوفَ الالتزام القانوني ويبلّغ النظام أنه استُوفي، وذلك أسوأ من الفشل بصوت عالٍ.

يجب أن يتخطى مسار المحو الآلية عمدًا:

$user->forceDelete();

أو، حيث يجب أن تبقى السجلات لأسباب ضريبية أو تدقيقية، جهّل بدل أن تزيل - استبدل الحقول الشخصية، واحتفظ بالصف، وسجّل أن ذلك حدث. وهذا قرار فيه محامٍ، وعلى الشيفرة تنفيذ الجواب الذي يعطيه لا الجواب الافتراضي.

الجدول ينمو فقط

لا شيء يقلّم الصفوف المحذوفة ناعمًا ما لم يكتب أحد المهمة. وبعد سنوات يصير الجدول في جزء كبير منه صفوفًا لا يراها أحد، وتصير الفهارس أكبر مما تحتاج، ويحمل كل استعلام شرط deleted_at is null على المخطِّط أن يحسب حسابه.

إن كان الـ trait على نموذج، فينبغي أن توجد سياسة حفظ ومهمة تفرضها. و"نحتفظ بكل شيء إلى الأبد" سياسة صالحة؛ أما ألا توجد سياسة فهو كيف يبلغ جدول أربعين مليون صف منها ستة ملايين تهم.

متى يكون صحيحًا فعلًا

حيث يكون التراجع ميزة يتوقعها المستخدمون - أرشيف، أو سلة مهملات، أو حذف بالخطأ ينبغي أن يستطيع الدعم عكسه.

حيث يكون السجل مشارًا إليه من تاريخ يجب أن يبقى مقروءًا: فاتورة تسمّي عميلًا أُزيل منذئذٍ ينبغي أن تظل تسمّيه.

حيث يكون الحذف سير عمل لا حدثًا، بخطوة موافقة بين التعليم والإزالة.

تلك مجموعة أضيق من "كل نموذج"، وهي حيث ينتهي الـ trait عادةً. ينبغي أن يكون الافتراض حذفًا حقيقيًا، مع إضافة الـ trait حيث يستطيع أحد أن يقول ما الذي يعنيه التراجع لذلك الجدول - وحيث أجاب أحد سؤال التفرّد وسؤال التقارير وسؤال الحفظ قبل حذف أول صف لا بعد أول تذكرة دعم.

قيود التفرد والتقارير أسئلة عن المخطط. وإضافة أيٍّ منها لاحقًا إلى جدول يحذف حذفًا ناعمًا منذ سنتين عملُ بيانات، ويستغرق أطول مما يتوقع الناس. أما سؤال المحو فمعلَّق به موعد قانوني، فموضعه مع القرارات الأخرى التي لا رجعة فيها.

أسئلة ذات صلة

هل نستخدم الحذف الناعم افتراضيًا؟
لا - اجعل الحذف الحقيقي هو الافتراض وأضف الـ trait حيث يوجد سبب. والسبب عادةً أن أحدًا يحتاج التراجع، أو أن السجل مشار إليه من تاريخ يجب أن يبقى مقروءًا. وتطبيقه في كل مكان بالعادة يحوّل كل جدول إلى أرشيف لا يقلّمه أحد وكل قيد إلى نصف حقيقة.
كيف نتعامل مع الأعمدة الفريدة؟
إما أن تُدخل طابع الحذف في الفهرس الفريد، فيُسمح بصف حي واحد وأي عدد من المحذوفة، وإما أن تكفّ عن استخدام القيمة الطبيعية كمفتاح فريد وتحرّرها عند الحذف بإعادة كتابتها. كلاهما خيار متعمَّد؛ وما لا يعمل فهرس فريد بسيط على جدول ذي حذف ناعم.
هل يلبّي الحذف الناعم طلب محو؟
لا، وهذه هي التي تحمل عاقبة قانونية لا هندسية. البيانات لا تزال هناك ولا تزال مقروءة، فلم يُمحَ شيء. طلب المحو يحتاج إزالة حقيقية أو تجهيلًا حقيقيًا، مع تخطٍّ صريح لآلية الحذف الناعم.
وماذا عن الصفوف التي تشير إلى المحذوف؟
تظل تشير إليه، لأن الصف لا يزال هناك والمفتاح الأجنبي لا يزال محقَّقًا. وذلك كثيرًا ما يكون ما أردته - فاتورة قديمة ما زالت تسمّي عميلها - وهو قرار يُتخذ لكل علاقة لا خاصية في الـ trait.

← العودة إلى كل المقالات

اتصل بنا+1 848 272 7583واتساب+90 850 308 5436البريدinfo@codefacture.comصفحة التواصل