Skip to main content

๐Ÿ“„ Booking and confirmation of a rental

The booking and confirmation of a rental is a process that involves the following steps:

  • The user selects a rental and prepopulate dates in a specific rental page.
  • The user click on the "Book" button.

Routesโ€‹

Lets view the routes in config/routes.yaml:

# book a rental
rental_book:
path: /rental/book/{rentalId}
controller: App\Controller\RentalController::book
methods: [POST]

Controllerโ€‹

The controller is located at src/Controller/RentalController.php. Lets add the book method:


/**
* Book action to book a rental
*
* @param Request $request - The request object
* @param string $id - The rental id
*
* @return Response - The response object
*/
#[Route('/rental/book/{rentalId}', name: 'rental_book', methods: ['POST'])]
public function book(Request $request, $rentalId): Response
{
// Fetch the rental by id
$rental = $this->_documentManager->getRepository(Rental::class)->find($rentalId);

// If the rental is not found, throw a 404 exception
if (! $rental) {
throw $this->createNotFoundException('rental not found');
}

// Get the start and end dates from the request

$startDateInput = $request->request->get('startDate');
$endDateInput = $request->request->get('endDate');

$startDate = $startDateInput ? new DateTime($startDateInput) : null;
$endDate = $endDateInput ? new DateTime($endDateInput) : null;




// Calculate total price based on night cost and number of days
$interval = $startDate->diff($endDate);
$days = $interval->days;
$totalPrice = $days * $rental->nightCost;

// Calculate new availability based on the booking dates
$newAvailability = $rental->calcAvailabilitySlots($startDate, $endDate, $rental->availability->toArray());

// Create and persist the booking
$booking = new Booking();
$booking->rental = $rental;
$booking->rentalName = $rental->name;
$booking->startDate = DateTimeImmutable::createFromMutable($startDate);
$booking->endDate = DateTimeImmutable::createFromMutable($endDate);
$booking->totalCost = (int)$totalPrice;
$rental->availability = $newAvailability;

// Persist the booking and rental
$this->_documentManager->persist($booking);
$this->_documentManager->persist($rental);
$this->_documentManager->flush();

// Redirect to a confirmation page or show confirmation message
return $this->render(
'rental/confirmation.html.twig', [
'rental' => $rental,
'booking' => $booking,
'totalPrice' => $totalPrice,
]
);
}

This function does the following:

  • Fetch the rental by id
  • Get the start and end dates from the request
  • Calculate total price based on night cost and number of days
  • Calculate new availability based on the booking dates

This helper function is used to calculate the new availability based on the booking dates, and diverge the availability slots:

Exampleโ€‹

Booking "Rental 1" for dates 2024-01-02 to 2024-01-05:


{
rental_name : "Rental 1",
...
availability : [
{ "start_date" : "2024-01-01",
"end_date" : "2024-01-10"
},
{ "start_date" : "2024-01-21",
"end_date" : "2025-01-01"
}
]
}

The availability slots are diverged into 3 slots:

  • 2024-01-01 until 2024-01-02
  • 2024-01-05 until 2024-01-10
  • 2025-01-21 until 2025-01-01

Lets add the helper function to the controller: src/Document/Rental.php:

  /**
* Function : calcAvailabilitySlots
*
* This function is responsible for calculating the
* availability slots based on the booking start and end dates.
*
* @param DateTime $bookingStart - The booking start date
* @param DateTime $bookingEnd - The booking end date
* @param array $availability - The availability array
*
* @return ArrayCollection - The new availability slots
*/
public function calcAvailabilitySlots(DateTime $bookingStart,
DateTime $bookingEnd,
array $availability
): ArrayCollection {
// Create a new ArrayCollection to store the new availability
$newAvailability = new ArrayCollection();

// Loop through each period in the availability to calculate the new availability
foreach ($availability as $period) {
$periodStart = $period->startDate;
$periodEnd = $period->endDate;

// Booking is entirely before this period
if ($bookingEnd < $periodStart) {
$newAvailability->add($period);
continue;
}

// Booking is entirely after this period
if ($bookingStart > $periodEnd) {
$newAvailability->add($period);
continue;
}

// Booking starts before the period and ends within it
if ($bookingStart <= $periodStart && $bookingEnd < $periodEnd) {
$newPeriod = new Availability();
$newPeriod->startDate = $bookingEnd->modify('+1 day');
$newPeriod->endDate = $periodEnd;
$newAvailability->add($newPeriod);
continue;
}

// Booking starts during the period and ends after it
if ($bookingStart > $periodStart && $bookingEnd >= $periodEnd) {
$newPeriod = new Availability();
$newPeriod->startDate= $periodStart;
$newPeriod->endDate = $bookingStart->modify('-1 day');
$newAvailability->add($newPeriod);
continue;
}

// Booking is entirely within the period
if ($bookingStart > $periodStart && $bookingEnd < $periodEnd) {
$newPeriod1 = new Availability();
$newPeriod1->startDate = $periodStart;
$newPeriod1->endDate = $bookingStart->modify('-1 day');
$newAvailability->add($newPeriod1);

$newPeriod2 = new Availability();
$newPeriod2->startDate = $bookingEnd->modify('+1 day');
$newPeriod2->endDate = $periodEnd;
$newAvailability->add($newPeriod2);
continue;
}

// Booking covers the entire period
// Do not add the period to newAvailability (effectively removing it)
}

return $newAvailability;
}
  • Create and persist the booking
  • Persist the booking and rental

Finally, the function redirects to a confirmation page or shows a confirmation message.

Confirmation Pageโ€‹

The confirmation page is located at templates/rental/confirmation.html.twig. This page shows the booking details and the total price.

{# templates/rental/confirmation.html.twig #}

{% extends 'base.html.twig' %}

{% block title %}Booking Confirmation{% endblock %}

{% block body %}
<div class="container">
<h2>Booking Confirmation</h2>
<p>Rental: {{ rental.name }}</p>
<p>Location: {{ rental.location }}</p>
<p>Check-in Date: {{ booking.startDate|date('Y-m-d') }}</p>
<p>Check-out Date: {{ booking.endDate|date('Y-m-d') }}</p>
<p>Total Price: {{ totalPrice }}$</p>

<a href="{{ path('rental_index') }}" class="btn btn-success my-3">Back</a>
</div>
{% endblock %}

Lets test the booking processโ€‹

  • Search a rental for a specific date
  • Navigate to the rental page and click on the "Book" button

Now try to find the same location for those dates and see that no rental is available.