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

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

جزء من قسم البرمجة كائنية التوجه في رحلة PHP على Coddy. الدرس 91 من 91.

challenge icon

التحدي

سهل

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

ستنظم التعليمات البرمجية عبر سبعة ملفات:

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

    تنسيق 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;
    }
}

?>

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم PHP عبر الإنترنت