Welcome to a tutorial on how to create a simple bus ticket booking system in PHP and MySQL. Working for a client who is going to run a fleet of buses? Or just doing this as an old-school assignment? Well, here’s a simple booking system that will kickstart your project. Read on!
TABLE OF CONTENTS
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
EXAMPLE CODE DOWNLOAD
Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
PHP MYSQL BUS TICKET BOOKING
All right, let us now get into more details on how the PHP MYSQL bus ticket booking system works.
PART 1) THE DATABASE
1A) SEATS TABLE
-- (A) AVAILABLE SEATS
CREATE TABLE `seats` (
`bus_id` varchar(16) NOT NULL,
`seat_id` varchar(16) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `seats`
ADD PRIMARY KEY (`seat_id`,`bus_id`);
INSERT INTO `seats` (`bus_id`, `seat_id`) VALUES
('BUS-A', 'A1'),
('BUS-A', 'A2'),
('BUS-A', 'A3'),
('BUS-A', 'A4'),
('BUS-A', 'B1'),
('BUS-A', 'B2'),
('BUS-A', 'B3'),
('BUS-A', 'B4'),
('BUS-A', 'C1'),
('BUS-A', 'C2'),
('BUS-A', 'C3'),
('BUS-A', 'C4');
This table should be self-explanatory, to store the seats available on the buses.
bus_id
Partial primary key, vehicle registration number.seat_id
Partial primary key.
Well, feel free to add more fields as required – Window seat, smoking allowed, or even invent your own “grid system” to properly store the seat layout.
1B) TRIPS TABLE
-- (B) TRIPS
CREATE TABLE `trips` (
`trip_id` bigint(20) NOT NULL,
`bus_id` varchar(16) NOT NULL,
`trip_date` datetime NOT NULL,
`trip_from` varchar(255) NOT NULL,
`trip_to` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `trips`
ADD PRIMARY KEY (`trip_id`),
ADD KEY `bus_id` (`bus_id`),
ADD KEY `trip_date` (`trip_date`);
ALTER TABLE `trips`
MODIFY `trip_id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
INSERT INTO `trips` (`trip_id`, `bus_id`, `trip_date`, `trip_from`, `trip_to`) VALUES
(1, 'BUS-A', '2077-06-05 00:00:00', 'Point A', 'Point B');
trip_id
Primary key, auto-increment.bus_id
Foreign key.trip_date
When the trip takes place.trip_from
Board the bus here.trip_to
Destination.
1C) RESERVATIONS TABLE
-- (C) RESERVATIONS
CREATE TABLE `reservations` (
`trip_id` bigint(20) NOT NULL,
`user_id` bigint(20) NOT NULL,
`email` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `reservations`
ADD PRIMARY KEY (`trip_id`, `user_id`),
ADD KEY `email` (`email`);
trip_id
Partial primary and foreign key.user_id
Partial primary and foreign key.email
Customer’s email.name
Customer’s name.
Some of you sharp code ninjas should have question marks over why we are storing the email and name, when we can retrieve the data from the user table. This is not redundant, customers may be booking for someone else or on behalf of their company.
P.S. Add more fields as necessary again.
1D) RESERVED SEATS
-- (D) RESERVED SEATS
CREATE TABLE `reserve_seats` (
`trip_id` bigint(20) NOT NULL,
`user_id` bigint(20) NOT NULL,
`seat_id` varchar(16) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `reserve_seats`
ADD PRIMARY KEY (`trip_id`, `user_id`, `seat_id`);
A “sub table” for reservations, to store which seats are selected. trip_id user_id seat_id
are all primary and foreign keys.
PART 2) PHP LIBRARY
<?php
class Bus {
// (A) CONSTRUCTOR - CONNECT TO DATABASE
private $pdo = null;
private $stmt = null;
public $error = null;
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 FUNCTION - RUN SQL QUERY
function query ($sql, $data=null) : void {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// (D) ADD/EDIT TRIP
function trip ($bus, $date, $from, $to, $tid=null) {
// (D1) SQL & DATA
if ($tid==null) {
$sql = "INSERT INTO `trips`
(`bus_id`, `trip_date`, `trip_from`, `trip_to`)
VALUES (?,?,?,?)";
$data = [$bus, $date, $from, $to];
} else {
$sql = "UPDATE `trips` SET
`bus_id`=?, `trip_date`=?, `trip_from`=?, `trip_to`=?
WHERE `trip_id`=?";
$data = [$bus, $date, $from, $to, $tid];
}
// (D2) RESULTS
$this->query($sql, $data);
return true;
}
// (E) GET GIVEN TRIP
function get ($tid) {
// (E1) TRIP DATA
$this->query("SELECT * FROM `trips` WHERE `trip_id`=?", [$tid]);
$trip = $this->stmt->fetch();
if (!is_array($trip)) { return false; }
// (E2) SEATS
$this->query(
"SELECT s.`seat_id`, r.`user_id`
FROM `seats` s
LEFT JOIN `trips` t USING (`bus_id`)
LEFT JOIN `reserve_seats` r USING(`seat_id`)
WHERE t.`trip_id`=?
ORDER BY s.`seat_id`",
[$tid]
);
$trip["seats"] = $this->stmt->fetchAll();
return $trip;
}
// (F) SAVE RESERVATION
function reserve ($tid, $uid, $email, $name, $seats) {
// (F1) RESERVATIONS TABLE
$this->query(
"INSERT INTO `reservations` (`trip_id`, `user_id`, `email`, `name`) VALUES (?,?,?,?)",
[$tid, $uid, $email, $name]
);
// (F2) SELECTED SEATS
$sql = "INSERT INTO `reserve_seats` (`trip_id`, `user_id`, `seat_id`) VALUES ";
foreach ($seats as $seat) {
$sql .= "(?,?,?),";
$data[] = $tid;
$data[] = $uid;
$data[] = $seat;
}
$sql = substr($sql, 0, -1);
$this->query($sql, $data);
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) NEW BUS BOOKING OBJECT
$_BUS = new Bus();
The library may look massive at first, but keep and look closely.
- (A, B, H) When
$_BUS = new Bus()
is created, the constructor connects to the database. The destructor closes the connection. - (C)
query()
A simple helper function to run an SQL statement. - (D To F) The “actual” booking functions.
trip()
Add or edit a trip.get()
Get the specified trip.reserve()
Save a reservation.
- (G) Self-explanatory. Change the settings to your own.
PART 3) TICKET BOOKING PAGE
3A) THE HTML
<?php
// (A) FIXED FOR THIS DEMO
$tripid = 1;
$userid = 999;
// (B) GET TRIP
require "2-bus-lib.php";
$trip = $_BUS->get($tripid);
?>
<!-- (C) TRIP INFO -->
<div class="info">
<div class="iLeft">Date</div>
<div class="iRight"><?=$trip["trip_date"]?></div>
</div>
<div class="info">
<div class="iLeft">From</div>
<div class="iRight"><?=$trip["trip_from"]?></div>
</div>
<div class="info">
<div class="iLeft">To</div>
<div class="iRight"><?=$trip["trip_to"]?></div>
</div>
<!-- (D) DRAW SEATS LAYOUT -->
<div id="layout"><?php foreach ($trip["seats"] as $s) {
$taken = is_numeric($s["user_id"]);
printf("<div class='seat%s'%s>%s</div>",
$taken ? " taken" : "",
$taken ? "" : " onclick='reserve.toggle(this)'",
$s["seat_id"]
);
} ?></div>
<!-- (E) LEGEND -->
<div class="legend">
<div class="box"></div> Open
</div>
<div class="legend">
<div class="box taken"></div> Taken
</div>
<div class="legend">
<div class="box selected"></div> Your selected seats
</div>
<!-- (F) SAVE RESERVATION -->
<form id="form" method="post" action="4-save.php" onsubmit="return reserve.save();">
<input type="hidden" name="tid" value="<?=$tripid?>">
<input type="hidden" name="uid" value="<?=$userid?>">
<label>Name</label>
<input type="text" name="name" required value="Jon Doe">
<label>Email</label>
<input type="email" name="email" required value="jon@doe.com">
<input type="submit" value="Reserve Seats">
</form>
Not going to explain the HTML page line-by-line, but a quick walkthrough:
- (A & B) Load the PHP library, and get information on the trip. The trip id and user id are fixed for this demo.
- (C) Information on the trip itself.
- (D) Seats layout and select a seat.
- (E) Seats legend.
- (F) “Checkout form”.
3B) THE JAVASCRIPT
var reserve = {
// (A) CHOOSE THIS SEAT
toggle : seat => seat.classList.toggle("selected"),
// (B) SAVE RESERVATION
save : () => {
// (B1) GET SELECTED SEATS
var selected = document.querySelectorAll("#layout .selected");
// (B2) ERROR!
if (selected.length == 0) {
alert("No seats selected.");
return false;
}
// (B3) NINJA FORM SUBMISSION
else {
var form = document.getElementById("form");
for (let seat of selected) {
let input = document.createElement("input");
input.type = "hidden";
input.name = "seats[]";
input.value = seat.innerHTML;
form.appendChild(input);
}
return true;
}
}
};
This one just simply inserts the selected seats into the HTML form before submission.
PART 4) “CHECKOUT”
<?php
// (A) LOAD LIBRARY
require "2-bus-lib.php";
// (B) SAVE
echo $_BUS->reserve(
$_POST["tid"], $_POST["uid"], $_POST["email"], $_POST["name"], $_POST["seats"]
) ? "OK" : "ERROR" ;
// print_r($_POST);
Right, this dummy “checkout page” simply saves the booking request. Complete your own –
- Require the customer to pay first?
- Send a confirmation email?
- User registration required?
- Show a nice “thank you” page.
You decide.
EXTRAS
That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.
COMPLETE YOUR OWN SYSTEM!
I can hear the trolls singing “this is not a complete system”! Well, this is a simple tutorial, there are plenty of things to consider:
- If you don’t already have an existing user system, see the links below.
- If you don’t already have an existing admin panel, see the links below.
- Decide if you require users to be registered before allowing them to book, open for public booking (no registration required), or auto register when they “checkout”.
- Decide if you want to restrict the number of seats a customer can book, or if they need to manually call to book the entire bus.
- Complete your own online payment if you require customers to pay before booking.
- Complete your own processes – Send email confirmation? Send SMS? Verifications required for cross-border services? Documents?
- Do your own reports.
- Beef up on your own security and checks.
It is impossible to create a “one size fits all system”, and I cannot provide free consultation for everyone – “Complete the system” is your own homework.
LINKS & REFERENCES
- Simple User Login With PHP MySQL – Code Boxx
- User Registration With Email Verification – Code Boxx
- Simple PHP MySQL Admin Panel – Code Boxx
- Simple User Role Management – Code Boxx
THE END
Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
Hello sir , i have imported 1-database.sql on phpmyadmin database and tried to run the 3a-reservation.php but it shows
“Fatal error: Uncaught PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘test.trips’ doesn’t exist in C:\xampp\htdocs\PHP\2-bus-lib.php:25 Stack trace: #0 C:\xampp\htdocs\PHP\2-bus-lib.php(25): PDOStatement->execute(Array) #1 C:\xampp\htdocs\PHP\2-bus-lib.php(51): Bus->query(‘SELECT * FROM `…’, Array) #2 C:\xampp\htdocs\PHP\3a-reservation.php(17): Bus->get(1) #3 {main} thrown in C:\xampp\htdocs\PHP\2-bus-lib.php on line 25”
this error can you please lead us with this problem.
As it is – The SQL file is not properly imported or wrong database settings.
Is it possible to use sqlite db?
Why not? PHP PDO is fully capable of connecting to not just MYSQL – https://www.php.net/manual/en/ref.pdo-sqlite.php
Hello sir! I need bus ticket reservation system in php with algorithm.. can you have this project?? If you have thn can you plzz help me??
Hi! The code is already released under the MIT license, feel free to hire your own web developers to work on it. Cheers!
https://code-boxx.com/faq/#hire
https://code-boxx.com/faq/#help