قواعد العمل في الكود
قلب الواجهة الخلفية: القواعد التي تحكم ما يُسمح به. ⏱ نحو 35 دقيقة.
- تميّز قواعد العمل من التحقق من المدخلات.
- تكتب القواعد في وحدة مستقلة بثوابت ودوال صغيرة.
- ترد على مخالفة القواعد بـ 409، وترتّب الفحوص ترتيبًا صحيحًا.
- تختبر القواعد بجدول حالات يشمل الحدود.
١ما المنطق التجاري؟
كل تطبيق يحمل قواعد من عالم العمل الحقيقي: «الشحن مجاني فوق 200 ريال»، «لا يُحجز أكثر من 3 مقاعد للمتدرب»، «لا يُسحب أكثر من الرصيد». هذه قواعد العمل Business rules، والكود الذي يطبّقها اسمه المنطق التجاري Business logic، وهو قلب الواجهة الخلفية.
| التحقق من المدخلات (الدرس 7) | قاعدة العمل | |
|---|---|---|
| السؤال | هل البيانات سليمة الشكل؟ | هل العملية مسموحة الآن؟ |
| مثال | الكمية عدد صحيح موجب | الكمية لا تتجاوز المتوفر في المخزون |
| تحتاج؟ | الطلب وحده | الطلب + حالة النظام (المخزون، الرصيد، الحجوزات) |
| الرمز عند الفشل | 422 | 409 Conflict غالبًا |
وأين نكتبها؟ لا في المتصفح (يُتجاوز)، ولا مبعثرة داخل HTML، بل في وحدة مستقلة تستدعيها كل الصفحات. فإن تغيّرت القاعدة غدًا («الشحن مجاني فوق 150») عدّلت سطرًا واحدًا.
٢اكتب القواعد في مكان واحد
هذه قواعد الدفع في متجر الواحة: خصم للأعضاء، وشحن حسب المدينة ومجاني فوق حد، ثم الضريبة. كلها في ملف rules.php، وصفحة الدفع تستدعي دالة واحدة:
<?phprequire_once __DIR__ . '/rules.php'; $subtotal = (float) ($_GET['subtotal'] ?? 0);$member = ($_GET['member'] ?? '') === '1';$city = $_GET['city'] ?? 'الرياض'; $bill = checkout($subtotal, $member, $city);foreach ($bill as $k => $v) { echo "$k: $v\n";}
<?php// rules.php: قواعد عمل متجر الواحة في مكان واحدconst FREE_SHIPPING_FROM = 200;const VAT = 0.15; function discountRate(float $subtotal, bool $member): float{ if ($member && $subtotal >= 300) return 0.10; if ($member) return 0.05; return 0;} function shipping(float $afterDiscount, string $city): float{ if ($afterDiscount >= FREE_SHIPPING_FROM) return 0; return $city === 'المدينة المنورة' ? 10 : 25;} function checkout(float $subtotal, bool $member, string $city): array{ $discount = round($subtotal * discountRate($subtotal, $member), 2); $after = $subtotal - $discount; $ship = shipping($after, $city); $vat = round(($after + $ship) * VAT, 2); return ['subtotal' => $subtotal, 'discount' => $discount, 'shipping' => $ship, 'vat' => $vat, 'total' => $after + $ship + $vat];}
ثلاث حالات: زائر بلا خصم يدفع شحنًا، وعضو في المدينة بخصم 5% وشحن 10، وعضو بطلب كبير خصمه 10% وشحنه مجاني.
const FREE_SHIPPING_FROM = 200;ثابت: قيمة لا تتغير أثناء التشغيل، وتسميته توضح معناها بدل رقم «سحري» مبعثر في الكود.- كل دالة تطبّق قاعدة واحدة، وتعيد قيمة ولا تطبع. فيمكن استخدامها في صفحة
HTMLأو فيAPIأو في تقرير. checkoutتركّب القواعد بالترتيب الصحيح: الخصم، ثم الشحن على المبلغ بعد الخصم، ثم الضريبة.
٣القواعد التي تعتمد على حالة النظام
بعض القواعد تحتاج معرفة الوضع الحالي: هل يوجد مخزون؟ كم حجز المتدرب؟ إن خالف الطلب القاعدة نرد بـ 409 Conflict: الطلب سليم الشكل، لكنه يتعارض مع حالة النظام:
<?phpsession_start();const MAX_SEATS = 3;$stock = ['PHP' => 5, 'SQL' => 0, 'React' => 12]; // المقاعد المتاحة لكل دورة $course = $_POST['course'] ?? '';$mine = $_SESSION['seats'] ?? []; if (!isset($stock[$course])) { http_response_code(422); exit('دورة غير معروفة');}if ($stock[$course] < 1) { http_response_code(409); exit("لا توجد مقاعد في دورة $course");}if (count($mine) >= MAX_SEATS) { http_response_code(409); exit('لا يمكن حجز أكثر من ' . MAX_SEATS . ' دورات');}if (in_array($course, $mine)) { http_response_code(409); exit("سبق أن حجزت $course");}$_SESSION['seats'][] = $course;http_response_code(201);echo "تم الحجز. دوراتك: " . implode('، ', $_SESSION['seats']);
لاحظ ترتيب الفحوص: الشكل أولًا (422)، ثم القواعد (409)، ثم التنفيذ (201).
٤اختبر قواعدك بالجداول
قبل أن تثق بقاعدة، جرّبها على حالات مختارة، خاصة الحدود: ما قبل الحد وعنده وبعده. هذا أسلوب يستخدمه مختبرو البرمجيات (مقرر 245). اكتب جدول حالات ثم شغّل الدالة عليها:
<?php// rules.php: قواعد عمل متجر الواحة في مكان واحدconst FREE_SHIPPING_FROM = 200;const VAT = 0.15; function discountRate(float $subtotal, bool $member): float{ if ($member && $subtotal >= 300) return 0.10; if ($member) return 0.05; return 0;} function shipping(float $afterDiscount, string $city): float{ if ($afterDiscount >= FREE_SHIPPING_FROM) return 0; return $city === 'المدينة المنورة' ? 10 : 25;} function checkout(float $subtotal, bool $member, string $city): array{ $discount = round($subtotal * discountRate($subtotal, $member), 2); $after = $subtotal - $discount; $ship = shipping($after, $city); $vat = round(($after + $ship) * VAT, 2); return ['subtotal' => $subtotal, 'discount' => $discount, 'shipping' => $ship, 'vat' => $vat, 'total' => $after + $ship + $vat];} $cases = [ [199.99, false, 'جدة'], [200, false, 'جدة'], [210, true, 'جدة'], // بعد خصم 5% يصير 199.5 ← لا شحن مجاني! [300, true, 'الرياض'],];foreach ($cases as [$s, $m, $c]) { $b = checkout($s, $m, $c); echo str_pad($s, 7) . ($m ? ' عضو ' : ' زائر') . " ← شحن {$b['shipping']}، المجموع {$b['total']}\n";}
الحالة الثالثة مفاجأة مفيدة: العضو اشترى بـ 210 لكنه دفع شحنًا، لأن القاعدة تطبَّق على المبلغ بعد الخصم. هل هذا ما يريده صاحب المتجر؟ هذا سؤال للعميل لا للمبرمج، وجدول الحالات كشفه قبل أن يشتكي زبون.
توقّع وتحقّق
«لا يُسحب أكثر من الرصيد» مثال على:
رمز الحالة المناسب لطلب يتعارض مع حالة النظام:
Conflict.const VAT = 0.15; فائدتها:
أين نكتب قواعد العمل؟
الخلاصة
- قاعدة العمل: هل العملية مسموحة الآن؟ وتعتمد على حالة النظام.
- القواعد في وحدة واحدة على الخادم، بثوابت
constبدل أرقام سحرية. - دوال القواعد تعيد قيمًا ولا تطبع، فتصلح للصفحات وللـ
API. - الترتيب: الشكل
(422)ثم القواعد(409)ثم التنفيذ(200/201). - جرّب الحدود: قبل الحد وعنده وبعده.
تحدٍّ برمجي: قواعد السحب من المحفظة
متوسطمحفظة المتدرب الإلكترونية في المقصف. اكتب في wallet.php دالة القواعد وردود الطلب.
- الرصيد يبدأ 50 ريالًا ويُحفظ في
$_SESSION['balance']. - طلب POST فيه
amount: إن لم يكن عددًا موجبًا ← 422 والنصمبلغ غير صحيح. - إن تجاوز الحد الأقصى للعملية الواحدة (ثابت
MAX_PER_TXقيمته 30) ← 409 والنصالحد الأقصى للعملية 30. - إن تجاوز الرصيد ← 409 والنص
الرصيد غير كافٍ. - وإلا اخصم المبلغ ورد بـ 200 والنص
تم. الرصيد: 30(بالرصيد الجديد).
👀 اعرض الاستجابة المطلوبة
POST /wallet.php(amount=20)POST /wallet.php(amount=-5)POST /wallet.php(amount=40)POST /wallet.php(amount=25)POST /wallet.php(amount=30)POST /wallet.php(amount=abc)
- الكود يعمل بلا أخطاء أو تحذيرات
- سحب 20 ← تم. الرصيد: 30
- سحب
-5← 422 - سحب 40 ← 409 (تجاوز الحد)
- سحب 40 ← الحد الأقصى للعملية 30
- سحب 25 ← تم. الرصيد: 5
- سحب 30 والرصيد 5 ← الرصيد غير كافٍ
(409) - abc ← 422 بلا تحذيرات
- الحد معرّف ثابتًا MAX_PER_TX
💡 تلميح 1
(422)، ثم الحد، ثم الرصيد (409).💡 تلميح 2
حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
<?phpsession_start();const MAX_PER_TX = 30;$_SESSION['balance'] = $_SESSION['balance'] ?? 50; $amount = filter_var($_POST['amount'] ?? '', FILTER_VALIDATE_FLOAT);if ($amount === false || $amount <= 0) { http_response_code(422); exit('مبلغ غير صحيح');}if ($amount > MAX_PER_TX) { http_response_code(409); exit('الحد الأقصى للعملية ' . MAX_PER_TX);}if ($amount > $_SESSION['balance']) { http_response_code(409); exit('الرصيد غير كافٍ');}$_SESSION['balance'] -= $amount;echo "تم. الرصيد: {$_SESSION['balance']}";