خدمة تأجير المركبات
جزء من قسم البرمجة كائنية التوجه في رحلة PHP على Coddy. الدرس 91 من 91.
التحدي
سهللنبنِ نظام Vehicle Rental Service يدير أسطولًا من المركبات، ويتتبع عمليات استئجار العملاء، ويحسب الأسعار بناءً على نوع المركبة. يجمع هذا التحدي الشامل بين التوريث والواجهات والتغليف ونمط State لإنشاء تطبيق واقعي لأعمال تأجير المركبات.
ستنظم التعليمات البرمجية عبر سبعة ملفات:
VehicleState.php: عرّف واجهةVehicleStateالتي تمثل الحالات المختلفة التي يمكن أن تكون عليها المركبة. أضف طريقتين:getStatus(): stringوcanRent(): bool. ثم أنشئ ثلاث فئات تطبق هذه الواجهة:AvailableState: تُرجع"Available"وtrueRentedState: تُرجع"Rented"وfalseMaintenanceState: تُرجع"Maintenance"وfalse
Vehicle.php: ضمّن ملف VehicleState. أنشئ فئة مجردةVehicleمع ترقية المُنشئ للمعاملاتlicensePlate(من النوع string وpublic readonly)، وbrand(من النوع string وpublic readonly)، وdailyRate(من النوع float وprotected). خزّن الحالة الحالية في خاصية private، مع تعيين القيمة الافتراضية إلىAvailableState. طبّق ما يلي:getState(): VehicleState: تُرجع الحالة الحاليةsetState(VehicleState $state): void: تغيّر الحالةgetStatus(): string: تفوّض إلىgetStatus()الخاصة بالحالةcanRent(): bool: تفوّض إلىcanRent()الخاصة بالحالةabstract calculateCost(int $days): float: يحسب كل نوع من المركبات التكلفة بطريقة مختلفةgetType(): string: تُرجع اسم الفئة باستخدامstatic::class
Vehicles.php: ضمّن ملف Vehicle. أنشئ ثلاث فئات ملموسة للمركبات:Car: ترث من Vehicle؛ تُرجعcalculateCost()القيمةdailyRate * daysMotorcycle: ترث من Vehicle؛ تُرجعcalculateCost()القيمةdailyRate * days * 0.8(خصم بنسبة 20%)Van: ترث من Vehicle مع معامل إضافي$mileageRate(من النوع float)؛ تُرجعcalculateCost(int $days, int $miles = 0)القيمة(dailyRate * days) + (mileageRate * miles)
Customer.php: أنشئ فئةCustomerمع ترقية المُنشئ للمعاملاتid(من النوع int وpublic readonly) وname(من النوع string وpublic readonly). تتبع سجل الاستئجار في مصفوفة private. طبّق ما يلي:addRental(string $vehiclePlate, float $cost): void: تضيف إلى السجلgetRentalCount(): int: تُرجع عدد عمليات الاستئجارgetTotalSpent(): float: تُرجع مجموع تكاليف جميع عمليات الاستئجار
Rental.php: ضمّن ملفي Vehicle وCustomer. أنشئ فئةRentalتربط عميلًا بمركبة. استخدم ترقية المُنشئ للمعاملاتcustomer(من النوع Customer وpublic readonly)، وvehicle(من النوع Vehicle وpublic readonly)، وdays(من النوع int وprivate)، وmilesاختياريًا (من النوع int وprivate وقيمته الافتراضية 0). طبّق ما يلي:calculateTotal(): float: إذا كانت المركبة من نوع Van، مرّر miles إلىcalculateCost()؛ وإلا فمرّر days فقطcomplete(): string: تحسب الإجمالي، وتضيفه إلى سجل العميل، وتعيّن حالة المركبة إلى AvailableState، وتُرجع"Rental completed: $[total]"(بمنزلتين عشريتين)
RentalService.php: ضمّن ملفي Rental وVehicles. أنشئ فئةRentalServiceتدير الأسطول. خزّن المركبات في مصفوفة private مفهرسة حسب لوحة الترخيص. طبّق ما يلي:addVehicle(Vehicle $vehicle): void: تضيف إلى الأسطولfindVehicle(string $plate): ?Vehicle: تُرجع المركبة أو nullgetAvailableVehicles(): array: تُرجع المركبات التي تكون فيهاcanRent()مساوية لـ truerentVehicle(Customer $customer, string $plate, int $days, int $miles = 0): string|Rental: إذا لم يتم العثور على المركبة، تُرجع"Vehicle not found"؛ وإذا لم تكن متاحة، تُرجع"Vehicle is not available"؛ وإلا فتعيّن حالة المركبة إلى RentedState وتُرجع كائن Rental جديدًاsetMaintenance(string $plate): string: تعيّن حالة المركبة إلى MaintenanceState؛ وتُرجع"[plate] set to maintenance"أو"Vehicle not found"
main.php: ضمّن ملفي RentalService وCustomer. ستتلقى ثلاث مدخلات: بيانات الأسطول (JSON)، وبيانات العميل (JSON)، والعمليات (JSON).تنسيق Fleet JSON:
[{"type": "car", "plate": "CAR-001", "brand": "Toyota", "rate": 50}, {"type": "van", "plate": "VAN-001", "brand": "Ford", "rate": 80, "mileage_rate": 0.25}]تنسيق Customer JSON:
{"id": 1, "name": "Alice"}تنسيق Operations JSON:
[{"op": "available"}, {"op": "rent", "plate": "CAR-001", "days": 3}, {"op": "complete", "plate": "CAR-001"}, {"op": "maintenance", "plate": "VAN-001"}, {"op": "customer_stats"}]أنشئ خدمة التأجير، وأضف جميع المركبات إلى الأسطول، وأنشئ العميل. تتبع عمليات الاستئجار النشطة حسب اللوحة. عالج كل عملية:
"available": اطبع"Available vehicles: [count]""rent": استأجر المركبة؛ وإذا نجح الأمر، خزّن Rental واطبع"Rented [plate] for [days] days"؛ وإذا كانت Van، فضمّن miles من العملية؛ وإلا فاطبع رسالة الخطأ"complete": أكمل عملية الاستئجار المخزنة لتلك اللوحة؛ واطبع النتيجة"maintenance": اطبع نتيجةsetMaintenance()"status": اطبع"[plate]: [status]"للوحة المحددة"customer_stats": اطبع"[name]: [count] rentals, $[total] spent"(بمنزلتين عشريتين)
اطبع كل نتيجة في سطر جديد.
يوضح هذا النظام كيف يعالج نمط State توافر المركبات بطريقة أنيقة دون شروط معقدة، وكيف ينشئ التوريث تسلسلًا هرميًا واضحًا للمركبات مع تسعير متعدد الأشكال، وكيف يحمي التغليف سلامة بيانات الاستئجار طوال سير العمل.
جرّب بنفسك
<?php
require_once 'RentalService.php';
require_once 'Customer.php';
// اقرأ المدخلات
$fleetJson = trim(fgets(STDIN));
$customerJson = trim(fgets(STDIN));
$operationsJson = trim(fgets(STDIN));
// حلّل بيانات JSON
$fleetData = (array)json_decode($fleetJson, true);
$customerData = (array)json_decode($customerJson, true);
$operations = (array)json_decode($operationsJson, true);
// TODO: أنشئ كائن RentalService
// TODO: أضف جميع المركبات إلى الأسطول بناءً على النوع:
// - "car" -> new Car(plate, brand, rate)
// - "motorcycle" -> new Motorcycle(plate, brand, rate)
// - "van" -> new Van(plate, brand, rate, mileage_rate)
// TODO: أنشئ كائن Customer من customerData
// TODO: أنشئ مصفوفة لتتبع التأجيرات النشطة حسب plate
$activeRentals = [];
// TODO: عالج كل عملية واطبع النتائج:
// - "available": Print "Available vehicles: [count]"
// - "rent": استأجر المركبة، خزّن Rental إذا نجح، اطبع النتيجة
// - "complete": أكمل التأجير، اطبع النتيجة
// - "maintenance": عيّن الصيانة، اطبع النتيجة
// - "status": اطبع "[plate]: [status]"
// - "customer_stats": اطبع "[name]: [count] rentals, $[total] spent"
foreach ($operations as $op) {
$operation = (array)$op;
$opType = $operation['op'];
// TODO: تعامل مع كل نوع عملية
switch ($opType) {
case 'available':
// TODO: Print available vehicle count
break;
case 'rent':
// TODO: أجّر المركبة واطبع النتيجة
break;
case 'complete':
// TODO: أكمل التأجير واطبع النتيجة
break;
case 'maintenance':
// TODO: عيّن الصيانة واطبع النتيجة
break;
case 'status':
// TODO: اطبع حالة المركبة
break;
case 'customer_stats':
// TODO: اطبع إحصائيات العميل
break;
}
}
?>جميع دروس البرمجة كائنية التوجه
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsالكلمة المفتاحية $thisالـ Methodsالـ Propertiesالمنشئ (__construct)الهادم (__destruct)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةالكلمة المفتاحية parent::إعادة تعريف الدوال (Method Overriding)الكلمة المفتاحية finalالأصناف المجردة (Abstract Classes)مراجعة - هيكلية الموظفين7التغليف (Encapsulation)
العام، المحمي، الخاص (Public, Protected, Private)نظرة متعمقة على معدلات الوصولدوال Getters و Settersإخفاء المعلوماتميزة Constructor Promotion (8.0)مراجعة - نظام سجلات الطلاب10مفاهيم البرمجة كائنية التوجه المتقدمة
التركيب مقابل الوراثةحقن التبعيةالأصناف المجهولةالتعدادات (PHP 8.1)الألياف (PHP 8.1)تعمق في استنساخ الكائناتالمولدات والمكررات13أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع2مساحات الأسماء والتحميل التلقائي
مقدمة إلى مساحات الأسماءالكلمة المفتاحية useمعيار التحميل التلقائي PSR-4أداة التحميل التلقائي Composerملخص - مشروع منظم5الواجهات والعقود
مقدمة إلى الواجهاتتنفيذ الواجهاتتنفيذ واجهات متعددةالواجهة مقابل الـ Abstract Classالـ Type Hinting مع الواجهاتملخص - حاسبة الأشكال8الميثودات السحرية (Magic Methods)
مقدمة في الميثودات السحرية__toString و __debugInfo__get, __set, __isset, __unset__call و __callStatic__clone واستنساخ الكائنات__serialize و __unserializeمراجعة - Custom Collection3خصائص الكلاس
خصائص الـ Instance مقابل الخصائص الساكنة (Static)الثوابت في الكلاساتالدوال والخصائص الساكنة (Static)الخصائص الخاصة (Private) والمحمية (Protected)خصائص للقراءة فقط (Readonly) في PHP 8.1مراجعة - مدير الحساب البنكي6تعدد الأشكال (Polymorphism)
مراجعة إعادة تعريف الدوال (Method Overriding)تعدد الأشكال عبر الـ Interfacesتلميح النوع (Type Hinting) وأنواع الاتحاد (Union Types)الربط الاستاتيكي المتأخر (Late Static Binding)ملخص - معالج الدفع (Payment Processor)تدرّب بنفسك: مترجم PHP عبر الإنترنت