خدمة تأجير المركبات
جزء من قسم Object Oriented Programming في رحلة PHP على Coddy — الدرس 91 من 91.
التحدي
سهللنقم ببناء نظام خدمة تأجير المركبات (Vehicle Rental Service) الذي يدير أسطولاً من المركبات، ويتتبع إيجارات العملاء، ويحسب الأسعار بناءً على نوع المركبة. يجمع هذا التحدي الشامل بين الوراثة (inheritance)، والواجهات (interfaces)، والتغليف (encapsulation)، ونمط الحالة (State pattern) لإنشاء تطبيق واقعي لأعمال التأجير.
ستقوم بتنظيم الكود الخاص بك عبر سبعة ملفات:
VehicleState.php— قم بتعريف واجهةVehicleStateالتي تمثل الحالات المختلفة التي يمكن أن تكون عليها المركبة. قم بتضمين طريقتين:getStatus(): stringوcanRent(): bool. ثم أنشئ ثلاث فئات (classes) تنفذ هذه الواجهة:AvailableState— تعيد"Available"وtrueRentedState— تعيد"Rented"وfalseMaintenanceState— تعيد"Maintenance"وfalse
Vehicle.php— قم بتضمين ملف VehicleState. أنشئ فئة مجردة (abstract class) باسمVehicleمع استخدام خاصية constructor promotion لكل منlicensePlate(string، public readonly)، وbrand(string، public readonly)، وdailyRate(float، protected). قم بتخزين الحالة الحالية في خاصية خاصة (private property)، مع جعل القيمة الافتراضية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. أنشئ ثلاث فئات مركبات ملموسة (concrete classes):Car— ترث من Vehicle؛ تعيدcalculateCost()القيمةdailyRate * daysMotorcycle— ترث من Vehicle؛ تعيدcalculateCost()القيمةdailyRate * days * 0.8(خصم 20%)Van— ترث من Vehicle مع معلمة (parameter) إضافية$mileageRate(float)؛ تعيدcalculateCost(int $days, int $miles = 0)القيمة(dailyRate * days) + (mileageRate * miles)
Customer.php— أنشئ فئةCustomerمع خاصية constructor promotion لكل منid(int، public readonly) وname(string، public readonly). تتبع سجل الإيجارات في مصفوفة خاصة (private array). قم بتنفيذ ما يلي:addRental(string $vehiclePlate, float $cost): void— تضيف إلى السجلgetRentalCount(): int— تعيد عدد الإيجاراتgetTotalSpent(): float— تعيد مجموع تكاليف جميع الإيجارات
Rental.php— قم بتضمين ملفي Vehicle و Customer. أنشئ فئةRentalتربط العميل بمركبة. استخدم constructor promotion لكل منcustomer(Customer، public readonly)، وvehicle(Vehicle، public readonly)، وdays(int، private)، واختياريًاmiles(int، private، القيمة الافتراضية 0). قم بتنفيذ ما يلي:calculateTotal(): float— إذا كانت المركبة من نوعVan، مرر الأميال (miles) إلىcalculateCost()؛ وإلا مرر الأيام فقطcomplete(): string— تحسب الإجمالي، وتضيفه إلى سجل العميل، وتضبط حالة المركبة إلىAvailableState، وتعيد"Rental completed: $[total]"(بمنزلتين عشريتين)
RentalService.php— قم بتضمين ملفي Rental و Vehicles. أنشئ فئةRentalServiceتدير الأسطول. قم بتخزين المركبات في مصفوفة خاصة مفهرسة برقم اللوحة (license plate). قم بتنفيذ ما يلي:addVehicle(Vehicle $vehicle): void— تضيف مركبة إلى الأسطولfindVehicle(string $plate): ?Vehicle— تعيد المركبة أو nullgetAvailableVehicles(): array— تعيد المركبات التي تكون فيهاcanRent()صحيحة (true)rentVehicle(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).تنسيق JSON للأسطول:
[{"type": "car", "plate": "CAR-001", "brand": "Toyota", "rate": 50}, {"type": "van", "plate": "VAN-001", "brand": "Ford", "rate": 80, "mileage_rate": 0.25}]تنسيق JSON للعميل:
{"id": 1, "name": "Alice"}تنسيق 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، قم بتضمين الأميال من العملية؛ وإلا اطبع رسالة الخطأ"complete"— أكمل الإيجار المخزن لتلك اللوحة؛ واطبع النتيجة"maintenance"— اطبع نتيجةsetMaintenance()"status"— اطبع"[plate]: [status]"للوحة المعطاة"customer_stats"— اطبع"[name]: [count] rentals, $[total] spent"(بمنزلتين عشريتين)
اطبع كل نتيجة في سطر جديد.
يوضح هذا النظام كيف يعالج نمط الحالة (State pattern) توفر المركبات بأناقة دون الحاجة إلى شروط معقدة، وكيف تنشئ الوراثة تسلسلاً هرمياً نظيفاً للمركبات مع تسعير متعدد الأشكال (polymorphic pricing)، وكيف يحمي التغليف سلامة بيانات الإيجار طوال سير العمل.
جرّب بنفسك
<?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: إنشاء مثيل (instance) من RentalService
// TODO: إضافة جميع المركبات إلى الأسطول بناءً على النوع:
// - "car" -> new Car(plate, brand, rate)
// - "motorcycle" -> new Motorcycle(plate, brand, rate)
// - "van" -> new Van(plate, brand, rate, mileage_rate)
// TODO: إنشاء مثيل (instance) من Customer من customerData
// TODO: إنشاء مصفوفة لتتبع الإيجارات النشطة حسب رقم اللوحة
$activeRentals = [];
// TODO: معالجة كل عملية وطباعة النتائج:
// - "available": طباعة "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: طباعة عدد المركبات المتاحة
break;
case 'rent':
// TODO: استئجار مركبة وطباعة النتيجة
break;
case 'complete':
// TODO: إكمال الإيجار وطباعة النتيجة
break;
case 'maintenance':
// TODO: تعيين الصيانة وطباعة النتيجة
break;
case 'status':
// TODO: طباعة حالة المركبة
break;
case 'customer_stats':
// TODO: طباعة إحصائيات العميل
break;
}
}
?>جميع دروس Object Oriented Programming
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)