Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

خدمة تأجير المركبات

جزء من قسم Object Oriented Programming في رحلة PHP على Coddy — الدرس 91 من 91.

challenge icon

التحدي

سهل

لنقم ببناء نظام خدمة تأجير المركبات (Vehicle Rental Service) الذي يدير أسطولاً من المركبات، ويتتبع إيجارات العملاء، ويحسب الأسعار بناءً على نوع المركبة. يجمع هذا التحدي الشامل بين الوراثة (inheritance)، والواجهات (interfaces)، والتغليف (encapsulation)، ونمط الحالة (State pattern) لإنشاء تطبيق واقعي لأعمال التأجير.

ستقوم بتنظيم الكود الخاص بك عبر سبعة ملفات:

  • VehicleState.php — قم بتعريف واجهة VehicleState التي تمثل الحالات المختلفة التي يمكن أن تكون عليها المركبة. قم بتضمين طريقتين: getStatus(): string و canRent(): bool. ثم أنشئ ثلاث فئات (classes) تنفذ هذه الواجهة:
    • AvailableState — تعيد "Available" و true
    • RentedState — تعيد "Rented" و false
    • MaintenanceState — تعيد "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 * days
    • Motorcycle — ترث من 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 — تعيد المركبة أو null
    • getAvailableVehicles(): 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