In Section 12, you implemented AJAX to fetch from the mowgliscafe.php web service we to get a menu represented as JSON, and then used localstorage to store menu data on an online cafe menu.
Today, we'll implement our own PHP web service (menu.php) using SQL and PHP's handy PDO class to store menu data (the same data from Thursday's section!). This web service will behave similarly to mowgliscafe.php but with some fresh updates!
The web service you used in Section 12 returned JSON for menu data. Let's use this to work backwards, building the table(s) in SQL that might help us organize a cafe menu with different categories, subcategories, etc.
{
"Drinks" : [{
"subcategory" : "Coffee",
"items" : [{ "name" : "Americano", "price" : "3.75", "cost" : "1.10" },
{ ... }, ... ]
... }],
"Bakery" : [{ ... }, { ... }, ... ]
}
JSON (schema)
Eventually, we'd like to output JSON from our SQL table that looks like this menu.json.
setup-cafe.sql
Here's an example table for the menu (in setup-cafe.sql). Were your columns and datatypes close?
CREATE TABLE menu(
id INT AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
category VARCHAR(50) NOT NULL,
subcategory VARCHAR(50),
price DECIMAL(4, 2), -- we can set precision to 2 decimals with DECIMAL
cost DECIMAL(4, 2),
-- an "id" column is often used for a PRIMARY KEY, but name
-- could also be a PRIMARY KEY if guaranteed unique
PRIMARY KEY(id)
);
SQL
Remember that there are often multiple ways to design a SQL table, with various trade-offs. You may find yourself changing your tables (or adding new ones) as your projects get more complex, but this one will help us implement our menu.
With the cafedb
database selected in phpMyAdmin, import setup-cafe.sql.
Select the menu
table that appears (initally empty).
Import menu-data.csv" to populate the menu (you should see 59 rows added)
Try adding a few INSERT statements in the MySQL console to add a few of our section's own items!)
With our SQL table populated, we'll use our new SQL database with PHP's PDO class to start implementing our own version of the menu.php web service!
As discussed in lecture on Friday, it's good practice to have a common PHP file to factor out shared settings for your database connection from PHP.
We have provided a menu.phps and common.phps (save as
.php files) to get started. This common.php is very similar to the one from lecture,
but factors out the PDO construction with a get_PDO()
function you can call to get a connected PDO object (but we need to fill out the db connection settings).
common.php also includes a few helper functions for handling 503 and 400 errors, which you will need to change for Part 4.
Our menu.php web serivce will support two GET requests:
menu.php?list=<items|categories>
will return a flat JSON array of all items or all categories in the menu, depending on the value passed.
menu.php
(no GET parameters) will return a more structured JSON object of items on the menu (similar to the response of the mowgliscafe.php web service you used in Section 12)First, we'll design the queries we need for each request, and test them in the SQL console in phpMyAdmin. These are the queries we will then use with the PDO object in PHP!
menu.php?list=items: What is the SQL query to get all name values in the menu table, ordered alphabetically?
SELECT name FROM menu ORDER BY name;
SQL
menu.php?list=categories: What is the SQL query to get all category values in the menu table, ordered alphabetically?
SELECT DISTINCT category FROM menu ORDER BY category;
SQL
menu.php: What is the SQL query to get the name, category, cost, and price of all items in the menu table, ordered by category and breaking ties by subcategory alphabetically?
SELECT name, category, cost, price FROM menu ORDER BY category, subcategory;
SQL
With these queries in hand, we'll use PHP's PDO object to connect our PHP with SQL!
# Variables for connections to the database.
$host = 'localhost'; # fill in with server name
$port = '8889' # fill in with a port if necessary (will be different mac/pc)
$user = 'root'; # fill in with user name
$password = 'root'; # fill in with password (will be different mac/pc)
$dbname = 'cafedb'; # fill in with db name
# Make a data source string that will be used in creating the PDO object
$ds = "mysql:host={$host}:{$port};dbname={$dbname};charset=utf8";
# connect to the cafedb database and set some attributes
$db = new PDO($ds, $user, $password);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
PHP (example)
You'll need to fill in the variables for your MAMP server, but we provide the rest of the get_PDO()
function in common.phps
To find find out what to use with phpMyAdmin, check out the MAMP WebStart page (screenshot here). For other systems you have to search on the web.
Once you have the PDO
object, you can use it to
query the database. PDO object's query function returns a
PDOStatement object
that contains the rows that match a query, which we'll want to loop through with the fetch function
to get the values we're going to use in our PHP code.
$rows = $db->query("SQL query string"); # fill in the query string
while ($row = $rows->fetch()) {
# We can now access the column values for each row!
}
PHP (template)
$rows = $db->query("SELECT name FROM menu ORDER BY name");
while ($row = $rows->fetch()) {
echo $row["name"] . "\n"; # "Americano"
}
PHP (example)
fetch
The query
returns a PDOStatement
To get the data out of the rows you either use a foreach
loop OR
you can fetchAll
to get all information from the statement
(fetch
will only get one row, whatever is "next" in the PDO object)
PDO::FETCH_BOTH
is the default fetch style, but there are others
$rows = $db->query("SELECT * FROM menu ORDER BY name LIMIT 1;");
$row = $rows->fetch(PDO::FETCH_ASSOC);
print_r($row); # Just get (all columns) for the first row
PHP
Array(
[id] => 34
[name] => Americano
[category] => Drinks
[subcategory] => Coffee
[price] => 3.75
[cost] => 1.10
)
printed output
Each assume $rows
contains the results of a query
while ($row = $rows->fetch()) {
print_r($row);
print("\n");
}
# PDO::FETCH_ASSOC only fetches the associative array
while ($row = $rows->fetch(PDO::FETCH_ASSOC)) {
print_r($row);
print("\n");
}
# PDO::FETCH_NUM only fetches by position
while ($row = $rows->fetch(PDO::FETCH_NUM)) {
print_r($row);
print("\n");
}
PHP
First, we'll tackle the GET request for listing categories or the names of menu items as flat arrays of strings.
Using the PDO, retrieve all items from the menu and return an array result as JSON, sorting the items by name alphabetically. An example response (abbreviated) is shown below:
["Americano", "Apple Cider", "Avocado Toast", ..., "White Chocolate Mocha"]
JSON
Next, we'll update menu.php to support listing category names instead of item names. How can you factor this out with the existing solution to reduce redundancy?
An example response is shown below:
["Bakery", "Drinks", "Entrees", "Salad"]
JSON
We've output our error messages in plain text in most of the web services so far. But you can also output them in JSON! We have provided two helper functions for error-handling in common.php. Change them as necessary to output JSON for the different errors.
Add a case in menu.php such that if list is passed with a value other than items or categories, a 400 error is output with the JSON format:
{ "error" : "Please pass a list of 'items' or 'categories'" }
JSON
While we're at it, we'd like our error responses to be consistent. We'll update the 503 errors to output the following JSON whenever a PDO connection fails (using try/catch for any code that uses the PDO object):
{ "error" : "HTTP/1.1 503 Service Unavailable" }
JSON
menu.php
Next, we'll build the more useful JSON that we saw as clients in Section 12. If no list parameter is passed (e.g. a client requests menu.php, retrieve all the name, category, price, and cost for all rows from the menu table and respond with JSON in the following format:
{
"<categoryname>" : [
{ "subcategory" : "<subcategoryname>",
"items" : [{ "name" : "<name>", "price" : <price>, "cost" : <cost> },
"name" : "<name>", "price" : <price>, "cost" : <cost> }, ... ]},
{ "subcategory" : "<subcategoryname>", ... }, ... ],
"<categoryname>" : [
{ "subcategory" : "<subcategoryname>",
"items" : [ ... ] }]
}
JSON (Schema)
You can find the expected output JSON for the data in menu-data.csv here.
This one's a bit trickier with nested arrays, but remember to test your PHP code incrementally!
One example solution is here: menu.phps