Talk with your neighbor: Try to list at least 5 Code Quality standards we expect to achieve for your JS in this course (e.g. as specific as "line lengths < 100 characters" or "good variable descriptive identifier names")
Did they include some of the following?
We will see how these transfer to PHP as we develop our web services
Important Course Details
Web Services with JSON
PHP File I/O
Web Services with File I/O
Web Services with File I/O and JSON
Associative arrays are arrays that have keys with assigned values (similar to Maps in Java, dictionaries in Python, or JSON objects in JS)
$tas = array("AA" => "Daniel Hsu", "AB" => "Chao Hsu Lin",
"AC" => "Jack Venberg", "AD" => "Sandy Yang",
"AE" => "Ann Shan", "AF" => "Manchen Jin",
"AG" => "Hudson Gilmore", "AH" => "Manny Munoz",
"AI" => "Will Bigelow", "AJ" => "Zach Wu");
$tas["ZZ"] = "Jeremy Zhang";
$ages = array(); # empty array (length 0)
$ages["Whitney"] = 18; # stores 17 at the location where "Whitney" is stored
foreach
and associative arraysforeach ($array_name as $key => $value) {
...
}
$tas = array("AA" => "Daniel Hsu", ...);
foreach ($tas as $section => $ta) {
echo "{$ta} leads section {$section}.\n";
}
Daniel Hsu leads section AA.
Chao Hsu Lin leads section AB.
Jack Venberg leads section AC.
Sandy Yang Chen leads section AD.
...
in_array
vs array_key_exists
in_array
tests
whether a value is in an array
array_key_exists
tests whether a key is in an array
$tas = array("AA" => "Daniel Hsu", ...);
in_array("AA", $tas); # false
in_array("Jeremy Zhang", $tas); # true
array_key_exists("AA", $tas); # true
For this example let's write a simple web service jsontest.php
that takes no query parameters and returns a JSON like the one
below.
This result will be used in a web page jsontest.html
,
jsontest.css
, and jsontest.js
.
{
"name": "Miranda",
"hobbies": [
"pottery",
"softball",
"cycling",
"watching youtube"
]
}
<body>
<header>
<h1>JSON Test</h1>
</header>
<section>
<button id="test-btn">Get JSON data.</button>
<h2>Response</h2>
<p id="response"></p>
</section>
</body>
function loadData(data) {
let response = id("response");
response.innerText = data["name"] + "'s Hobbies";
let ul = document.createElement("ul");
response.appendChild(ul);
for(let i = 0; i < data["hobbies"].length; i++) {
let li = document.createElement("li");
li.innerText = data["hobbies"][i];
ul.appendChild(li);
}
}
header
header("Content-type: type/subtype");
Most of the time we'll use
header("Content-type: text/plain");
or
header("Content-type: application/json");
Also remember that in error cases we want to send back the Invalid Request header
header("HTTP/1.1 400 Invalid Request");
Remember that you can not print
or echo
before a header
statement but it doesn't have to be the first line of code.
Helpful Slides from Tuesday's section on what header information in the Network's tab for Text and HTML look like are here, look below for "application/json"
This example shows the result of setting Content-Type as application/json
and the JSONs in the response
Try using the following steps in jsontest.php
$output = array()
for example )
json_encode($output)
to "stringify" your associative array into a
JSON formatted string.
header("Content-Type: application/json");
)
NOTE: we can also use json_decode to convert JSON strings into PHP arrays.
<?php
header("Content-Type: application/json");
$output = array();
$output["name"] = "Miranda";
$output["hobbies"] = array("pottery", "softball",
"cycling", "watching youtube");
print(json_encode($output));
?>
Produces:
{
"name":"Miranda",
"hobbies":["pottery","softball", "cycling", "watching youtube"]
}
<backstory>
It's week 7 and I'm ready for a vacation, but I can't decide
where to go. So I want a tool that will randomly pick from a list of
"Points of Interests" (vacation spots) and give me some guidance.
I know I have already built the randomizer and JSONTest - so I want something similar...
</backstory>
To start we will use a modified version of JSONtest as a prototype
Let's first determine how the users will interact with the API. Questions we can ask might be:
For Points of Interest we're looking for:
The general format of the response could look like this for all of our cities:
{
"cities" : [
{
"name": "Seattle",
"sites": [
{
"name": "Space Needle",
"image": "....",
"review": ".... "
}
]
},
{
"name": "New York",
"sites": [
{
"name": "Ellis Island",
"image": "....",
"review": ".... "
},
{
"name": "Statue of Liberty",
"image": "....",
"review": ".... "
}
]
}
...
]
}
Given the information we may store in the server, how might we want to view it?
split
to view different parts)?
For our Points of Interest API we want three types of data:
For each Point of Interest we want:
$_GET
and $_POST
indices for those
requests are set (using isset
), and handle the error correctly if not.
$_GET
vs $_POST
where
needed (for now we're using $_GET
).
How will your data be stored on the server?
How will you convert the data into the right format for sending back to the caller?
Write a simple web service that accepts a city
and
outputs an attraction that you should visit in that city
http://example.com/pointsofinterest.php?city=Seattle
The best place to visit in Seattle is Space Needle.
See below for sample code
<?php
header("Content-type: text/plain");
if (isset($_GET["city"])) {
$attractions = get_list();
$city = $_GET["city"];
echo "The best place to visit in " . $city . " is " .
$attractions[$city] . ".\n";
}
function get_list() {
$attractions = array("Seattle" => "Space Needle",
"New York" => "Ellis Island",
...
"London" => "Big Ben");
}
?>
What if we want to return all of the cities/points of interest?
http://example.com/pointsofinterest.php?city=all
{
"Seattle":"Space Needle",
"New York":"Ellis Island",
...
"San Franciso":"Fisherman's Warf"
}
See below for sample code
Solution
<?php
if (isset($_GET["city"])) {
$attractions = get_list();
$city = $_GET["city"];
if (strcmp($city, "all") === 0) {
header("Content-type: application/json");
print_r(json_encode($attractions));
}
}
function get_list() {
$attractions = array("Seattle" => "Space Needle",
...);
}
?>
That's all great, but I don't want to have my PHP file get bloated with the associative array. There has to be a better way to store this information.
Besides, I'm still missing the images, and multiple Points of Interest per city!
From Wikipedia
function name(s) | category |
---|---|
file, file_get_contents, file_put_contents | reading/writing entire files |
basename, file_exists, filesize, fileperms, filemtime, is_dir, is_readable, is_writable, disk_free_space | asking for information |
copy, rename, unlink, chmod, chgrp, chown, mkdir, rmdir | manipulating files and directories |
glob, scandir | reading directories |
contents of foo.txt | file("foo.txt") | file_get_contents("foo.txt") |
---|---|---|
Hello how r u? I'm fine |
array("Hello\n", #0 "how r u?\n", #1 "\n", #2 "I'm fine\n" #3 ) |
"Hello\n how r u\n # a single \n # string I'm fine\n" |
The file
function returns lines of a file as an array (\n at end of each).
file_get_contents
returns entire contents of a file as a single string.
file_put_contents
writes a string into a file.
A quick note because you will see this in some of the PHP documentation
function name(parameterName=value, ..., parameterName=value) {
statements;
}
function print_separated($str, $separator=", ") {
if (strlen($str) < 0) {
echo $str[0];
for ($i = 1; $i < strlen($str); $i++) {
echo $separator . $str[$i];
}
}
}
print_separated("hello"); # h, e, l, l, o
print_separated("hello", "-"); # h-e-l-l-o
If no value is passed, the default will be used (defaults must come last)
file
Functionfile
returns the lines of a file as an array of strings.
But each ends with \n; to strip it, use an optional second parameter:
$cities = file("cities.txt");
foreach ($cities as $city) { # for ($i = 0; $i < count($cities); $i++)
echo $city; # no newline needed here!
}
$lines = file("cities.txt", FILE_IGNORE_NEW_LINES);
foreach ($cities as $city) {
echo "{$city}\n"; # a newline is now needed!
}
Common idiom: foreach or for loop over lines of file
# reverse a file
$text = file_get_contents("poem.txt");
$text = strrev($text);
file_put_contents("poem.txt", $text);
file_get_contents
returns entire contents of a file as a string
file_put_contents
writes a string into a file, replacing its old
contents
# add a new line to a file
$new_text = "P.S. ILY, GTG TTYL!~";
file_put_contents("poem.txt", $new_text, FILE_APPEND);
old contents | new contents |
---|---|
Roses are red
Violets are blue All my base Are belong to you. |
Roses are red
Violets are blue All my base Are belong to you. P.S. ILY, GTG TTYL!~ |
file_put_contents
can be called with an optional third parameter to append
(add to
end) rather than overwrite.
$arr = explode(delimiter, string);
$str = implode(delimiter, array);
$s = "CSE 154 A";
$a = explode(" ", $s); # ("CSE", "154", "A")
$s2 = implode("...", $a); # "CSE...154...A"
explode
and implode
convert between strings and arrays.
For more complex strings, you can use regular expressions.
explode
Seattle, Space Needle
New York, Ellis Island
Boston, Boston Harbor
Philadelphia, Valley Forge
foreach (file("cities.txt", FILE_IGNORE_NEW_LINES) as $city) {
$tokens = explode(",", $city);
echo "The best place to visit in " . $tokens[0] .
" is " . $tokens[1] . ".\n";
}
list
The list function "unpacks" an array into a set of variables.
When you now a file or line's exact length/format, use file and list to unpack it
list($var1, ..., $varN) = array;
Kyle Thayer
(206) 154 2017
17-154-0123
list($name, $phone, $ssn) = file("personal.txt");
...
list($area_code, $prefix, $suffix) = explode(" ", $phone);
function | description |
---|---|
glob | returns an array of all file names that match a given pattern (returns a file path and name, such as "foo/bar/myfile.txt") |
scandir | returns an array of all file names in a given directory (returns just the file names, such as "myfile.txt") |
Can accept a general path with the * wildcard (more powerful).
Suppose we store information about each Point of Interest in a file in a city subdirectory.
The name of the file is the name of the Point of Interest
Each file contains 2 lines
glob
Example# Get all of the information from the cities
$cities = glob("cities/*/*");
foreach ($cities as $city) {
$text = file_get_contents($city);
echo (basename($city) . " : " . $text . "\n");
}
glob can match a wildcard path with the * character
glob("foo/bar/*.doc")
returns all .doc files in the foo/bar
subdirectoryglob("food*")
returns all files whose names begin with "food"The basename
function strips any leading directory from a file path
basename("foo/bar/baz.txt")
returns "baz.txt"scandir
Exampleforeach (scandir("cities/") as $city) {
echo "I found a city: {$city}\n";
}
I found a city: .
I found a city: ..
I found a city: Ann_Arbor
I found a city: Beijing
...
scandir
includes current directory (".") and parent ("..") in the array.
Don't need basename
with scandir
; returns file names only
without directory
The pointsofinterest.zip example has been put together for you to look explore (note that it is not yet finished, and also could use some refactoring to meet the Code Quality Guide!)
Where did you see adherence to the similar code quality guidlines as for JS?
What was similar?
What was different?