Skip to content

Instantly share code, notes, and snippets.

@code-boxx
Last active May 26, 2023 05:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save code-boxx/66abdcf977807b9510f8e7ff733c52cb to your computer and use it in GitHub Desktop.
Save code-boxx/66abdcf977807b9510f8e7ff733c52cb to your computer and use it in GitHub Desktop.
PHP MYSQL Hotel Booking

PHP MYSQL HOTEL BOOKING SYSTEM

https://code-boxx.com/hotel-booking-management-php-mysql/

NOTES

  1. Create a database and import 1-database.sql.
  2. Change the database settings in 2-lib-hotel.php to your own.
  3. Launch 4a-reservation.php in the browser.

LICENSE

Copyright by Code Boxx

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

-- (A) ROOMS
CREATE TABLE `rooms` (
`room_id` varchar(255) NOT NULL,
`room_type` varchar(1) NOT NULL,
`room_price` decimal(12,2) DEFAULT 0.00
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `rooms`
ADD PRIMARY KEY (`room_id`),
ADD KEY `room_type` (`room_type`);
INSERT INTO `rooms` (`room_id`, `room_type`, `room_price`) VALUES
('#01-A', 'S', '10.00'),
('#01-B', 'S', '10.00'),
('#02-A', 'D', '20.00'),
('#02-B', 'T', '20.00'),
('#03-A', 'B', '30.00'),
('#04-A', 'P', '40.00');
-- (B) RESERVATIONS
CREATE TABLE `reservations` (
`reservation_id` bigint(20) NOT NULL,
`room_id` varchar(255) NOT NULL,
`reservation_start` date NOT NULL,
`reservation_end` date NOT NULL,
`reservation_name` varchar(255) NOT NULL,
`reservation_email` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `reservations`
ADD PRIMARY KEY (`reservation_id`),
ADD KEY `reservation_name` (`reservation_name`),
ADD KEY `reservation_email` (`reservation_email`),
ADD KEY `room_id` (`room_id`);
ALTER TABLE `reservations`
MODIFY `reservation_id` bigint(20) NOT NULL AUTO_INCREMENT;
<?php
class Hotel {
// (A) CONSTRUCTOR - CONNECT TO THE DATABASE
private $pdo = null;
private $stmt = null;
public $error;
function __construct () {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
// (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
function __destruct () {
if ($this->stmt !== null) { $this->stmt = null; }
if ($this->pdo !== null) { $this->pdo = null; }
}
// (C) HELPER - RUN SQL QUERY
function query ($sql, $data=null) : void {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// (D) SAVE ROOM
function save ($id, $type, $price, $oid=null) {
// (D1) SQL & DATA
$sql = $oid==null
? "INSERT INTO `rooms` (`room_id`, `room_type`, `room_price`) VALUES (?,?,?)"
: "UPDATE `rooms` SET `room_id`=?, `room_type`=?, `room_price`=? WHERE `room_id`=?" ;
$data = [$id, $type, $price];
if ($oid!=null) { $data[] = $oid; }
// (D2) RUN SQL
$this->query($sql, $data);
return true;
}
// (E) GET ROOMS FOR SELECTED DATE RANGE
function get ($from=null, $to=null) {
// (E1) GET ALL ROOMS
$this->query("SELECT * FROM `rooms`");
$rooms = [];
while ($r = $this->stmt->fetch()) {
$rooms[$r["room_id"]] = [
"t" => ROOM_TYPE[$r["room_type"]],
"p" => $r["room_price"]
];
}
// (E2) INCLUDE RESERVATIONS
if ($from && $to) {
$this->query(
"SELECT * FROM `reservations`
WHERE (`reservation_start` BETWEEN ? AND ?)
OR (`reservation_end` BETWEEN ? AND ?)",
[$from, $to, $from, $to]
);
while ($r = $this->stmt->fetch()) { if (isset($rooms[$r["room_id"]])) {
// (E2-1) ASSUMPTION - MORNING CHECKOUT + AFTERNOON CHECKIN
// ALLOW "SAME DAY RESERVATION" IF END DATE IS SAME
if ($r["reservation_end"] == $from) { continue; }
// (E2-2) MARK AS "BOOKED"
if (!isset($rooms[$r["room_id"]]["b"])) { $rooms[$r["room_id"]]["b"] = []; }
$rooms[$r["room_id"]]["b"][] = [
"s" => $r["reservation_start"],
"e" => $r["reservation_end"]
];
}}
}
// (E3) RETURN RESULTS
return $rooms;
}
// (F) SAVE RESERVATION
function reserve ($id, $start, $end, $name, $email) {
$this->query(
"INSERT INTO `reservations` (`room_id`, `reservation_start`, `reservation_end`, `reservation_name`, `reservation_email`)
VALUES (?,?,?,?,?)", [$id, $start, $end, $name, $email]
);
return true;
}
}
// (G) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (H) ROOM TYPES & RULES
define("ROOM_TYPE", [
"S" => "Single", "D" => "Double", "T" => "Twin",
"B" => "Business", "P" => "Presidential"
]);
define("MIN_BOOK", 1); // min next day
define("MAX_BOOK", 30); // max next month
define("MIN_STAY", 1); // min 1 day stay
define("MAX_STAY", 7); // max 7 days stay
// (I) START
$_HOTEL = new hotel();
<?php
if (isset($_POST["req"])) {
require "2-lib-hotel.php";
switch ($_POST["req"]) {
// (A) GET AVAILABLE ROOMS
case "get":
echo json_encode($_HOTEL->get($_POST["from"], $_POST["to"]));
break;
// (B) RESERVE ROOM
case "reserve":
echo $_HOTEL->reserve($_POST["id"], $_POST["start"], $_POST["end"], $_POST["name"], $_POST["email"])
? "OK" : $_HOTEL->error ;
break;
}
}
<!DOCTYPE html>
<html>
<head>
<title>Room Reservation</title>
<meta charset="utf-8">
<link rel="stylesheet" href="4c-reservation.css">
<script src="4b-reservation.js"></script>
</head>
<body>
<?php
// (A) SOME DATE CALCULATIONS
require "2-lib-hotel.php";
$min = date("Y-m-d", strtotime("+".MIN_BOOK."days"));
$max = date("Y-m-d", strtotime("+".MAX_BOOK."days"));
?>
<!-- (B) SELECT DATE -->
<form id="sDate" onsubmit="return rsv.get()">
<div class="step">STEP 1 OF 3</div>
<h1 class="head">SELECT DATE</h1>
<label>Check In</label>
<input type="date" id="sDateFrom"
min="<?=$min?>" max="<?=$max?>" value="<?=$min?>">
<label>Staying For (Days)</label>
<input type="number" id="sDateTo"
min="<?=MIN_STAY?>" max="<?=MAX_STAY?>" value="<?=MIN_STAY?>">
<input type="submit" value="Next" class="button">
</form>
<!-- (C) SELECT ROOM -->
<div id="sRoom" class="hide"></div>
<!-- (D) ENTER CONTACT INFO -->
<form id="sContact" class="hide" onsubmit="return rsv.reserve()">
<div class="step">STEP 3 OF 3</div>
<h1 class="head">CONTACT INFO</h1>
<label>Name</label>
<input type="text" name="name" required>
<label>Email</label>
<input type="email" name="email" required>
<input type="button" value="Back" class="button" onclick="rsv.switch(1)">
<input type="submit" value="Submit" class="button">
</form>
</body>
</html>
var rsv = {
// (A) HELPER - AJAX FETCH
fetch : (data, after) => {
// (A1) FORM DATA
let form;
if (data instanceof FormData) { form = data; }
else {
form = new FormData();
for (let [k, v] of Object.entries(data)) { form.append(k, v); }
}
// (A2) FETCH
fetch("3-ajax-hotel.php", { method : "post", body : form })
.then(res => res.text())
.then(txt => after(txt))
.catch(err => console.error(err));
},
// (B) PROPERTIES
hSec : null, // html sections
date : null, // currently selected date
room : null, // currently selected room
// (C) INITIALIZE - GET HTML SECTIONS
init : () => rsv.hSec = [
document.getElementById("sDate"),
document.getElementById("sRoom"),
document.getElementById("sContact")
],
// (D) SWITCH HTML SECTIONS
switch : i => { for (let j in rsv.hSec) {
if (i==j) { rsv.hSec[j].classList.remove("hide"); }
else { rsv.hSec[j].classList.add("hide"); }
}},
// (E) GET ROOMS FOR SELECTED DATE PERIOD
get : () => {
// (E1) GET DATE
rsv.date = {
days : parseInt(document.getElementById("sDateTo").value),
from : document.getElementById("sDateFrom").value
}
rsv.date.to = new Date(rsv.date.from);
rsv.date.to = new Date(
rsv.date.to.setDate(rsv.date.to.getDate() + rsv.date.days)
).toISOString().substring(0, 10);
// (E2) FETCH ROOMS
rsv.fetch({ req : "get", ...rsv.date }, res => {
// (E2-1) DRAW SELECTED DATE
rsv.hSec[1].innerHTML = "";
let row = document.createElement("div");
row.className = "rHead";
row.innerHTML = `<div class="step">STEP 2 OF 3</div>
<h1 class="head">SELECT A ROOM</h1>
<div class="step">FROM ${rsv.date.from} TO ${rsv.date.to} (${rsv.date.days} DAYS)</div>`;
rsv.hSec[1].appendChild(row);
// (E2-2) DRAW ROOMS
for (let [i,r] of Object.entries(JSON.parse(res))) {
row = document.createElement("div");
row.className = "rRow";
row.innerHTML = `<div class="rType">${r.t}</div>`;
if (r.b) {
row.classList.add("rBooked");
let s = '<div class="rStat">This room is booked.<ul>';
for (let b of r.b) { s += `<li>${b.s} (PM) to ${b.e} (AM)</li>`; }
s += '</ul></div>';
row.innerHTML += s;
} else {
row.innerHTML += `<div class="rStat">
Reserve this room for $${(rsv.date.days * r.p).toFixed(2)}.
</div>`;
row.onclick = () => rsv.set(i);
}
rsv.hSec[1].appendChild(row);
}
// (E2-3) BACK BUTTON
row = document.createElement("input");
row.type = "button";
row.className = "button";
row.value = "Back";
row.onclick = () => rsv.switch(0);
rsv.hSec[1].appendChild(row);
// (E2-4) DONE - SHOW PAGE
rsv.switch(1);
});
return false;
},
// (F) SELECT ROOM
set : id => {
rsv.room = id;
rsv.switch(2);
},
// (G) SUBMIT RESERVATION
reserve : () => {
// (G1) FORM DATA
let data = new FormData(rsv.hSec[2]);
data.append("req", "reserve");
data.append("id", rsv.room);
data.append("start", rsv.date.from);
data.append("end", rsv.date.to);
// (G2) AJAX FETCH
rsv.fetch(data, res => {
if (res=="OK") { location.href = "5-thank-you.html"; }
else { alert(res); }
});
return false;
}
};
window.onload = rsv.init;
/* (A) WHOLE PAGE */
* {
font-family: Arial, Helvetica, sans-serif;
box-sizing: border-box;
}
body {
max-width: 500px;
padding: 20px;
margin: 0 auto;
background: #f5f5f5;
}
/* (B) SHARED */
/* (B1) FORM SECTIONS */
#sDate, #sRoom, #sContact {
padding: 20px;
border: 1px solid #e7e7e7;
background: #fff;
}
.hide { display: none !important; }
/* (B2) COMMON ELEMENTS */
.head, .step { text-align: center; }
.head {
font-size: 30px;
font-weight: 500;
margin: 5px 0;
color: #e12323;
}
.step {
font-weight: 700;
font-size: 15px;
color: #ffa4a4;
}
/* (B3) FORM ELEMENTS */
label, input[type=text], input[type=email], input[type=number], input[type=date] {
width: 100%;
display: block;
}
label {
font-size: 16px;
color: #a3a3a3;
padding: 20px 0 10px 0;
}
input { padding: 10px; }
input[type=text], input[type=email], input[type=number], input[type=date] {
border: 0;
padding: 10px;
border: 1px solid #e5e5e5;
background: #f9f9f9;
}
.button {
margin-top: 30px;
padding: 10px 30px;
font-weight: 700;
border: 0;
color: #fff;
background: #b72626;
cursor: pointer;
}
/* (C) SELECT ROOM */
.rHead { margin-bottom: 30px; }
.rRow {
padding: 10px;
margin-bottom: 10px;
border: 1px solid #e5e5e5;
background: #f9f9f9;
position: relative;
cursor: pointer;
}
.rRow::after {
content : "\27A4";
color: #c1c1c1;
display: block;
height: 100%;
position: absolute;
top: 0; right: 8px;
display: flex;
align-items: center;
}
.rBooked {
color: #8f8f8f;
background: #ededed;
cursor: not-allowed;
}
.rType { font-weight: 700; }
.rStat {
font-size: 14px;
margin-top: 5px;
}
<!DOCTYPE html>
<html>
<head>
<title>Thank You</title>
<meta charset="utf-8">
</head>
<body>
<h1>Thank You</h1>
<p>Reservation received.</p>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment