Simple PHP Food Ordering System (Free Download)

Welcome to a tutorial on how to create a simple food ordering system in PHP and MYSQL. So you have picked up a project for a restaurant, or maybe you are stuck with this as a school assignment? Well, here is a quick sharing of mine – 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

Source code on GitHub Gist

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 FOOD ORDERING

Let us now get into the details of the food ordering system. But a disclaimer – This is more of an “in-store ordering kiosk” and not an online ordering platform.

 

PART 1) THE DATABASE

 

1A) MENU

1-database.sql
-- (A) MENU
CREATE TABLE `menu` (
  `item_id` bigint(20) NOT NULL,
  `item_name` varchar(255) NOT NULL,
  `item_desc` varchar(255) DEFAULT NULL,
  `item_img` varchar(255) DEFAULT NULL,
  `item_price` decimal(12,2) NOT NULL DEFAULT 0.00
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

ALTER TABLE `menu`
  ADD PRIMARY KEY (`item_id`),
  ADD KEY `item_name` (`item_name`);

ALTER TABLE `menu`
  MODIFY `item_id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;

Let us address the elephant in the room. We need a database to store the menu items and orders. This one should be pretty self-explanatory, the available menu items.

 

1B) ORDERS

1-database.sql
-- (B) ORDERS
CREATE TABLE `orders` (
  `order_id` bigint(20) NOT NULL,
  `order_date` datetime NOT NULL DEFAULT current_timestamp(),
  `order_status` tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

ALTER TABLE `orders`
  ADD PRIMARY KEY (`order_id`),
  ADD KEY `order_date` (`order_date`),
  ADD KEY `order_status` (`order_status`);

ALTER TABLE `orders`
  MODIFY `order_id` bigint(20) NOT NULL AUTO_INCREMENT;

Yep, there is no customer information in the orders. This is an “ordering kiosk” and not an “online ordering platform”. If you want, you can add the customer information on your own.

 

1C) ORDER ITEMS

1-database.sql
-- (C) ORDER ITEMS
CREATE TABLE `order_items` (
  `order_id` bigint(20) NOT NULL,
  `item_id` bigint(20) NOT NULL,
  `item_name` varchar(255) NOT NULL,
  `item_price` decimal(12,2) NOT NULL,
  `item_qty` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 
ALTER TABLE `order_items`
  ADD PRIMARY KEY (`order_id`,`item_id`);

The items being ordered. The item name and price are captured at the time of checkout for “data integrity”, they are not “redundant”.

 

 

1D) ORDER TOTALS

1-database.sql
-- (D) ORDER TOTALS
CREATE TABLE `order_totals` (
`order_id` bigint(20) NOT NULL,
`total_id` bigint(20) NOT NULL,
`total_name` varchar(255) NOT NULL,
`total_amt` decimal(12,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
 
ALTER TABLE `order_totals`
  ADD PRIMARY KEY (`order_id`,`total_id`);

Lastly, a table for the totals – Subtotal, discount, taxes, surcharge, grand total, etc…

 

PART 2) PHP FOOD ORDERING LIBRARY

2A) INIT

2-lib.php
class Order {
  // (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; }
  }
  // ...
}
 
// (I) 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", "");
 
// (J) ORDER STATUS
define("ORDER_STAT", [
  0 => "Preparing", 1 => "Ready", 2 => "Delivered"
]);
 
// (K) START!
$_ORD = new Order();
session_start();
if (!isset($_SESSION["cart"])) { $_SESSION["cart"] = []; }

This may be intimidating for some beginners, but keep calm and look closely.

  • (A, B, K) When $_ORD = new Order() is created, the constructor connects to the database. The destructor closes the connection.
  • (I) Remember to change the database settings to your own.
  • (J) The order status or process flow – Feel free to change this.
  • (K) Take note that we are using $_SESSION to handle the user’s shopping cart. More on that later.

 

 

2B) LIBRARY FUNCTIONS

2-lib.php
// (C) EXECUTE SQL QUERY
function query ($sql, $data=null) : void {
  $this->stmt = $this->pdo->prepare($sql);
  $this->stmt->execute($data);
}
 
// (D) GET ALL MENU ITEMS
function getMenu () {
  $this->query("SELECT * FROM `menu`");
  return $this->stmt->fetchAll();
}
 
// (E) GET ITEMS IN CART
function getCart () {
  // (E1) SQL QUERY
  $this->query(sprintf(
    "SELECT * FROM `menu` WHERE `item_id` IN (%s)",
    substr(str_repeat("?,", count($_SESSION["cart"])), 0, -1)
  ), array_keys($_SESSION["cart"]));
 
  // (E2) GET DATA
  $cart = ["i"=>[], "t"=>0];
  while ($r = $this->stmt->fetch()) {
    $cart["i"][$r["item_id"]] = [
      "n" => $r["item_name"], // name
      "q" => $_SESSION["cart"][$r["item_id"]], // quantity
      "p" => $r["item_price"] * $_SESSION["cart"][$r["item_id"]] // price
    ];
    $cart["t"] += $cart["i"][$r["item_id"]]["p"];
  }
  return $cart;
}

// (F) CHECKOUT
function checkout () : void {
  // (F1) EMPTY CART
  if (count($_SESSION["cart"])==0) { exit("Cart is empty."); }
 
  // (F2) "MAIN ORDER"
  $this->pdo->beginTransaction();
  $this->query("INSERT INTO `orders` (`order_status`) VALUES (0)");
  $oid = $this->pdo->lastInsertId();
 
  // (F3) ORDER ITEMS
  $cart = $this->getCart();
  $i = 1;
  foreach ($cart["i"] as $item) {
    $this->query(
      "INSERT INTO `order_items` (`order_id`, `item_id`, `item_name`, `item_price`, `item_qty`) VALUES (?, ?, ?, ?, ?)",
      [$oid, $i, $item["n"], $item["p"], $item["q"]]
    );
    $i++;
  }

  // (F4) ORDER TOTALS
  $this->query(
    "INSERT INTO `order_totals` (`order_id`, `total_id`, `total_name`, `total_amt`) VALUES (?, ?, ?, ?)",
    [$oid, 1, "total", $cart["t"]]
  );
 
  // (F5) DONE
  $this->pdo->commit();
  $_SESSION["cart"] = [];
  echo "OK";
}

// (G) GET ALL INCOMPLETE ORDERS
function getOrders () {
  $orders = [];
  $this->query("SELECT * FROM `orders` WHERE `order_status`<2");
  foreach ($this->stmt->fetchAll() as $o) {
    $orders[$o["order_id"]] = $o;
    $this->query("SELECT `item_name`, `item_price`, `item_qty` FROM `order_items` WHERE `order_id`=?", [$o["order_id"]]);
    $orders[$o["order_id"]]["i"] = $this->stmt->fetchAll();
    $this->query("SELECT `total_name`, `total_amt` FROM `order_totals` WHERE `order_id`=?", [$o["order_id"]]);
    $orders[$o["order_id"]]["t"] = $this->stmt->fetchAll();
  }
  return $orders;
}

// (H) UPDATE ORDER
function update ($id) : void {
  $this->query(
    "UPDATE `orders` SET `order_status`=`order_status`+1 WHERE `order_id`=?",
    [$id]
  );
}

Not going to explain every function line-by-line, so a quick summary:

  • query() Helper function to run an SQL query.
  • getMenu() Get all the menu items.
  • getCart() Get all the items in the shopping cart.
  • checkout() Checkout the current shopping cart.
  • getOrders() Get all the incomplete orders.
  • update() Update an order.

 

 

PART 3) ORDERING PAGE

 

3A) THE HTML

3a-kiosk.php
<!-- (A) MENU ITEMS -->
<div id="menu"><?php
  require "2-lib.php";
  foreach ($_ORD->getMenu() as $item) { ?>
  <div class="item">
    <img class="item-img" src="<?=$item["item_img"]?>">
    <div class="item-name">
      <?=$item["item_name"]?> 
      $<?=$item["item_price"]?>
    </div>
    <div class="item-desc"><?=$item["item_desc"]?></div>
    <input class="item-add" type="button" value="Add To Cart" onclick="cart.add(<?=$item["item_id"]?>)">
  </div>
  <?php } ?>
</div>
 
<!-- (B) CART -->
<div id="cart"></div>

This is the screenshot right at the top.

  1. Use the PHP library to get all the menu items, draw the HTML.
  2. An empty <div> to show what is in the current shopping cart.

 

3B) THE JAVASCRIPT

3b-kiosk.js
var cart = {
  // (A) PROPERTIES
  hCart : null, // html cart div
 
  // (B) AJAX HELPER
  ajax : opt => {
    // (B1) FORM DATA
    let form = new FormData();
    for (let [k,v] of Object.entries(opt.data)) {
      form.append(k, v);
    }
 
    // (B2) AJAX FETCH
    fetch("3c-cart.php", { method:"post", body:form })
    .then(res => res.text())
    .then(res => opt.ok(res))
    .catch(err => console.error(err));
  },

  // (C) CART INIT
  init : () => {
    cart.hCart = document.getElementById("cart");
    cart.list();
  },
 
  // (D) LIST CART
  list : () => cart.ajax({
    data : { req : "list" },
    ok : res => cart.hCart.innerHTML = res
  }),
 
  // (E) ADD ITEM TO CART
  add : id => cart.ajax({
    data : { req : "add", id : id },
    ok : res => cart.list()
  }),
 
  // (F) SET ITEM QUANTITY
  set : (id, qty) => cart.ajax({
    data : { req : "set", id : id, qty : qty },
    ok : res => cart.list()
  }),
 
  // (G) REMOVE ITEM FROM CART
  del : id => cart.ajax({
    data : { req : "del", id : id },
    ok : res => cart.list()
  }),
 
  // (H) CHECKOUT
  checkout : () => cart.ajax({
    data : { req : "checkout" },
    ok : res => {
      if (res=="OK") {
        alert("Checkout successful!");
        // @TODO - UP TO YOU
      } else { alert(res); }
      cart.list();
    }
  })
};
window.onload = cart.init;

Very “scary”. But look carefully, we are pretty much just doing AJAX calls to 3c-cart.php to drive the cart. The end.

 

 

3C) AJAX CART HANDLER

3c-cart.php
<?php
require "2-lib.php";
if (isset($_POST["req"])) { switch($_POST["req"]) {
  // (A) LIST CART ITEMS
  case "list":
    if (count($_SESSION["cart"]) > 0) {
      // (A1) CART ITEMS
      $cart = $_ORD->getCart();
      foreach($cart["i"] as $id=>$item) { ?>
      <div class="cart-row">
        <div class="cart-del" onclick="cart.del(<?=$id?>)">X</div>
        <div class="cart-item">
          <?=$item["n"]?>
          $<?=$item["p"]?>
        </div>
        <input class="cart-qty" type="number" value="<?=$item["q"]?>" min="1" max="99" onchange="cart.set(<?=$id?>, this.value)">
      </div>
      <?php }
 
      // (A2) TOTAL
      printf("<div class='cart-row total'>
        <div class='cart-item'>Total</div>
        <div>$%.2f</div>
      </div>", $cart["t"]);
 
      // (A3) CHECKOUT
      echo "<input class='cart-checkout' type='button' value='Checkout' onclick='cart.checkout()'>";
    } else { echo "<div class='cart-row'>Cart is empty.</div>"; }
    break;
 
  // (B) ADD ITEM TO CART
  case "add":
    if (isset($_SESSION["cart"][$_POST["id"]])) {
      $_SESSION["cart"][$_POST["id"]]++;
    } else {
      $_SESSION["cart"][$_POST["id"]] = 1;
    }
    break;
 
  // (C) SET QUANTITY
  case "set":
    $_SESSION["cart"][$_POST["id"]] = $_POST["qty"];
    break;
 
  // (D) REMOVE ITEM
  case "del":
    unset($_SESSION["cart"][$_POST["id"]]);
    break;
 
  // (E) CHECKOUT
  case "checkout":
    $_ORD->checkout();
    break;
}}

How the shopping cart works – $_SESSION["cart"][ITEM-ID] = QUANTITY.

 

PART 4) DUMMY ADMIN

 

4A) THE HTML

4a-admin.php
<!-- (A) UPDATE ORDER -->
<form id="upform" method="post">
  <input type="hidden" id="upid" name="id">
</form>
 
<?php
require "2-lib.php";
if (isset($_POST["id"])) { $_ORD->update($_POST["id"]); }
?>
 
<!-- (B) ORDERS LIST -->
<div id="orders"><?php
  foreach ($_ORD->getOrders() as $id=>$o) { ?>
  <div class="order" onclick="up(<?=$id?>)">
    <!-- (B1) ORDER DATA -->
    <div class="order-id">#<?=$id?></div>
    <div>
      <div class="order-stat"><?=ORDER_STAT[$o["order_status"]]?></div>
    </div>
    <div class="order-time"><?=$o["order_date"]?></div>

    <!-- (B2) ORDER ITEMS -->
    <ul class="order-items"><?php
    foreach ($o["i"] as $i) {
      printf("<li>%s x %d</li>", $i["item_name"], $i["item_qty"]);
    }
    ?></ul>
  </div>
  <?php }
?></div>

Just a dummy admin page to list all the orders, click on an order to update the order status.

 

4B) THE JAVASCRIPT

4b-admin.js
function up (id) {
  document.getElementById("upid").value = id;
  document.getElementById("upform").submit();
}

 

EXTRAS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

JUST A STARTING POINT

Yes, we have only scratched the surface with all of the above. There are a lot of blanks that you have to fill in by yourself:

  • Complete the ordering process, it’s different for everyone:
    • Pending – Preparing – Ready – Collected?
    • Pending – Payment – Prepare – Ready – Collected?
    • Delivery? Self-collect? Send to table number? Queue number system?
    • Cancellations and refunds?
  • Implement a user system if you want “more than just a kiosk”.
  • If there is an Internet connection, it is possible to process online payments – Paypal, Apple Pay, Google Pay, Amazon Pay, etc…
  • Security and checks.
    • A proper admin panel.
    • More checks for “valid cart items” during ordering and checkout.

 

A WAY BETTER SHOPPING CART

A shopping cart with product options, categories, discount coupons, user system, admin panel, API, and an installable web app - Click here to check out my eBook!

 

LINKS & REFERENCES

 

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!