Servicio de alquiler de vehículos
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy — lección 91 de 91.
Desafío
FácilVamos a construir un sistema de Servicio de Alquiler de Vehículos que gestione una flota de vehículos, realice el seguimiento de los alquileres de los clientes y calcule los precios en función del tipo de vehículo. Este desafío integral reúne herencia, interfaces, encapsulamiento y el patrón State para crear una aplicación de negocio de alquiler realista.
Organizarás tu código en siete archivos:
VehicleState.php— Define una interfazVehicleStateque represente los diferentes estados en los que puede estar un vehículo. Incluye dos métodos:getStatus(): stringycanRent(): bool. Luego crea tres clases que implementen esta interfaz:AvailableState— devuelve"Available"ytrueRentedState— devuelve"Rented"yfalseMaintenanceState— devuelve"Maintenance"yfalse
Vehicle.php— Incluye el archivo VehicleState. Crea una clase abstractaVehiclecon promoción de constructor paralicensePlate(string, public readonly),brand(string, public readonly) ydailyRate(float, protected). Almacena el estado actual en una propiedad privada, por defecto enAvailableState. Implementa:getState(): VehicleState— devuelve el estado actualsetState(VehicleState $state): void— cambia el estadogetStatus(): string— delega algetStatus()del estadocanRent(): bool— delega alcanRent()del estadoabstract calculateCost(int $days): float— cada tipo de vehículo calcula de forma diferentegetType(): string— devuelve el nombre de la clase usandostatic::class
Vehicles.php— Incluye el archivo Vehicle. Crea tres clases de vehículos concretas:Car— extiende de Vehicle;calculateCost()devuelvedailyRate * daysMotorcycle— extiende de Vehicle;calculateCost()devuelvedailyRate * days * 0.8(20% de descuento)Van— extiende de Vehicle con un parámetro adicional$mileageRate(float);calculateCost(int $days, int $miles = 0)devuelve(dailyRate * days) + (mileageRate * miles)
Customer.php— Crea una claseCustomercon promoción de constructor paraid(int, public readonly) yname(string, public readonly). Realiza el seguimiento del historial de alquileres en un array privado. Implementa:addRental(string $vehiclePlate, float $cost): void— añade al historialgetRentalCount(): int— devuelve el número de alquileresgetTotalSpent(): float— devuelve la suma de todos los costes de alquiler
Rental.php— Incluye los archivos Vehicle y Customer. Crea una claseRentalque vincule a un cliente con un vehículo. Usa promoción de constructor paracustomer(Customer, public readonly),vehicle(Vehicle, public readonly),days(int, private) y, opcionalmente,miles(int, private, por defecto 0). Implementa:calculateTotal(): float— si el vehículo es una Van, pasa miles acalculateCost(); de lo contrario, solo pasa dayscomplete(): string— calcula el total, lo añade al historial del cliente, establece el vehículo enAvailableStatey devuelve"Rental completed: $[total]"(con 2 decimales)
RentalService.php— Incluye los archivos Rental y Vehicles. Crea una claseRentalServiceque gestione la flota. Almacena los vehículos en un array privado indexado por la matrícula (license plate). Implementa:addVehicle(Vehicle $vehicle): void— añade a la flotafindVehicle(string $plate): ?Vehicle— devuelve el vehículo o nullgetAvailableVehicles(): array— devuelve los vehículos dondecanRent()es truerentVehicle(Customer $customer, string $plate, int $days, int $miles = 0): string|Rental— si no se encuentra el vehículo, devuelve"Vehicle not found"; si no está disponible, devuelve"Vehicle is not available"; de lo contrario, establece el vehículo enRentedStatey devuelve un nuevo objeto RentalsetMaintenance(string $plate): string— establece el vehículo enMaintenanceState; devuelve"[plate] set to maintenance"o"Vehicle not found"
main.php— Incluye los archivos RentalService y Customer. Recibirás tres entradas: datos de la flota (JSON), datos del cliente (JSON) y operaciones (JSON).Formato JSON de la flota:
[{"type": "car", "plate": "CAR-001", "brand": "Toyota", "rate": 50}, {"type": "van", "plate": "VAN-001", "brand": "Ford", "rate": 80, "mileage_rate": 0.25}]Formato JSON del cliente:
{"id": 1, "name": "Alice"}Formato JSON de las operaciones:
[{"op": "available"}, {"op": "rent", "plate": "CAR-001", "days": 3}, {"op": "complete", "plate": "CAR-001"}, {"op": "maintenance", "plate": "VAN-001"}, {"op": "customer_stats"}]Crea el servicio de alquiler, añade todos los vehículos a la flota y crea el cliente. Mantén un registro de los alquileres activos por matrícula. Procesa cada operación:
"available"— Imprime"Available vehicles: [count]""rent"— Alquila el vehículo; si tiene éxito, almacena el Rental e imprime"Rented [plate] for [days] days"; si es Van, incluye miles de la operación; de lo contrario, imprime el mensaje de error"complete"— Completa el alquiler almacenado para esa matrícula; imprime el resultado"maintenance"— Imprime el resultado desetMaintenance()"status"— Imprime"[plate]: [status]"para la matrícula dada"customer_stats"— Imprime"[name]: [count] rentals, $[total] spent"(con 2 decimales)
Imprime cada resultado en una línea nueva.
Este sistema muestra cómo el patrón State maneja elegantemente la disponibilidad de vehículos sin condicionales complejos, cómo la herencia crea una jerarquía de vehículos limpia con precios polimórficos y cómo el encapsulamiento protege la integridad de los datos de alquiler a lo largo de todo el flujo de trabajo.
Pruébalo tú mismo
<?php
require_once 'RentalService.php';
require_once 'Customer.php';
// Leer entradas
$fleetJson = trim(fgets(STDIN));
$customerJson = trim(fgets(STDIN));
$operationsJson = trim(fgets(STDIN));
// Analizar datos JSON
$fleetData = (array)json_decode($fleetJson, true);
$customerData = (array)json_decode($customerJson, true);
$operations = (array)json_decode($operationsJson, true);
// TODO: Crear instancia de RentalService
// TODO: Agregar todos los vehículos a la flota según el tipo:
// - "car" -> new Car(plate, brand, rate)
// - "motorcycle" -> new Motorcycle(plate, brand, rate)
// - "van" -> new Van(plate, brand, rate, mileage_rate)
// TODO: Crear instancia de Customer a partir de customerData
// TODO: Crear un array para rastrear alquileres activos por matrícula
$activeRentals = [];
// TODO: Procesar cada operación e imprimir resultados:
// - "available": Imprimir "Available vehicles: [count]"
// - "rent": Alquilar vehículo, almacenar Rental si tiene éxito, imprimir resultado
// - "complete": Completar el alquiler, imprimir resultado
// - "maintenance": Establecer mantenimiento, imprimir resultado
// - "status": Imprimir "[plate]: [status]"
// - "customer_stats": Imprimir "[name]: [count] rentals, $[total] spent"
foreach ($operations as $op) {
$operation = (array)$op;
$opType = $operation['op'];
// TODO: Manejar cada tipo de operación
switch ($opType) {
case 'available':
// TODO: Imprimir el recuento de vehículos disponibles
break;
case 'rent':
// TODO: Alquilar vehículo e imprimir resultado
break;
case 'complete':
// TODO: Completar el alquiler e imprimir resultado
break;
case 'maintenance':
// TODO: Establecer mantenimiento e imprimir resultado
break;
case 'status':
// TODO: Imprimir el estado del vehículo
break;
case 'customer_stats':
// TODO: Imprimir estadísticas del cliente
break;
}
}
?>Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - Calculadora de Formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeResumen - Colección personalizada3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy15Desafíos finales
Plataforma de E-LearningSistema bancarioDesarrollo de personajes de videojuegosServicio de alquiler de vehículos