Lab 7: PHP File/IO and Web Services

Lab Goals

By the end of this Lab, you should be able to:

  • Develop basic PHP debugging strategies
  • Set error reporting (help when developing) and 400 error handling (help for clients) in PHP
  • Find and process files within a directory
  • Write a PHP web service that outputs a response based on different GET query parameters

Setting Response Error Codes with PHP

Just like any other program you may write, it's important to cleanly handle errors with your PHP web service implementation to help clients know what went wrong. Often, errors come from mis-typing query parameters, passing in parameter values that don't correspond to data that was found on the server, etc.

When something goes wrong based on the client's request, we use the 400 error code. In PHP, you can set this in the response using the header function as follows:


header("HTTP/1.1 400 Invalid Request");
header("Content-type: text/plain");

PHP

Note that in case of an error, you may need two calls to the header function, once to set the error code, and the second time to set the Content-Type.

Debugging Tool: PHP Error Mode

PHP tends to be a little more silent in its errors. When developing, this is important to be aware of. One thing you can use to help report more errors is to use the following function call at the top of every PHP file you write:


          error_reporting(E_ALL);
          

PHP

This command makes PHP report more errors (sort of like "use strict" in JavaScript).

From Yesterday: Reading/Writing Files

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.

Example: Reading/Writing an Entire File

# reverse a file
$text = file_get_contents("poem.txt");
$text = strrev($text);
file_put_contents("poem.txt", $text);

PHP

file_get_contents returns entire contents of a file as a string

  • If the file doesn't exist, you will get a warning and an empty return string

file_put_contents writes a string into a file, replacing its old contents

  • If the file doesn't exist, it will be created

Appending to a File

# add a new line to a file
$new_text = "P.S. ILY, GTG TTYL!~";
file_put_contents("poem.txt", $new_text, FILE_APPEND);

PHP

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.

Reading Directories

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).

glob Example

# reverse all poems in the poetry directory
$poems = glob("poetry/poem*.dat");
foreach ($poems as $poemfile) {
  $text = file_get_contents($poemfile);
  file_put_contents($poemfile, strrev($text));
  echo "I just reversed " . basename($poemfile) . "\n";
}

PHP

glob can match a wildcard path with the * character

  • glob("foo/bar/*.doc") returns all .doc files in the foo/bar subdirectory
  • glob("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 Example

foreach (scandir("taxes/old") as $filename) {
  echo "I found a file: {$filename}\n";
}

PHP

I found a file: .

I found a file: ..

I found a file: 2007_w2.pdf

I found a file: 2006_1099.doc

output

scandir includes current directory (".") and parent ("..") in the array.

Don't need basename with scandir; returns file names only without directory

Pair Programming Opportunity

In this lab, we are encouraging students to work with a peer (e.g. your neighbor) to discuss and work through writing some neat PHP web services. If you'd like to try your hand at a challenging but popular pair programming technique known as "driver and navigator", read the slide below and try it out on one or both of the following exercises - ask your neighbors if they are interested!

What is Pair Programming?

A challenging programming technique that involves two people working together on a single computer. It provides an opportunity to explore new problem solving approaches, especially when learning a new language.

Role Description
Driver The person at the keyboard that takes instructions from the navigator
Navigator The person providing instruction (code and text editor commands) to the driver

"Driver" vs. "Navigator"

In order to have an effective pair programming experience, it's good to refer to the details of these roles (which in practice, alternate between tasks)

Drivers

  • Your main objective is simply to follow your navigator's instructions.
  • If you don't understand what a navigator is telling you, ask questions. However, if you don't understand why they're telling you something, wait until you're done with that section of code to ask questions.

Navigators

  • Communicate your ideas in a meaningful way that the driver can understand.
  • Make sure that the driver is only ever given 1 command to implement at a time.

If you're a navigator and you don't know what to do next, or you're a driver and you have an idea you'd like to try implementing, then switch roles with your partner.

Exercise 1: Gallery of Thrones

Given a directory of character pictures (got.zip), write a PHP web service which vends these photos. These pictures represent characters in one of three "houses" (or families) in the Game of Thrones series: House Lannister, House Stark, and House Targaryen (yes, we know there are more, but you can always add more pictures!).

There are two parts in this exercise. The first outputs all image names in the images folder, and the second filters the output based on an optional query parameter (house).

Go "down" to work through the slides specific to this exercise.

Exercise 1: Part 1 (Default Output)

First, write your web service such that it simply prints out (in plain text) a list of image names in the provided images folder. When the PHP page is executed, all of the file names in the images directory should be output, each on its own line.
Expected Output:

images/lannister1.jpg
images/lannister2.jpg
images/lannister3.jpg
images/lannister4.jpg
images/stark1.jpg
images/stark2.jpg
images/stark3.jpg
images/stark4.jpg
images/stark5.jpg
images/targaryen2.jpg
images/targaryen3.jpg
images/targaryen4.jpg

Expected output (plain text)

Exercise 1: Handling an Optional Query Parameter

After implementing the behavior when no query parameter is passed, add a query parameter handler to let a user filter the outputted images based on a house name. There are three three houses ("lannister", "stark", or "targaryen") that correspond to images in the images folder. If a value for house is included as a query parameter, your code should print only the images which are related to a specific house.

Print out the list of files including the images/ directory. This is loosely based on the assumption that your PHP webservice and your HTML/CSS/JS application would be served out of the same directory where images/ sits.

Exercise 1: Solution

Source (PHP)

Running Version (PHP)

Exercise 2: Recipe Generator

It's always fun to try new things. When it comes to cooking, sometimes it's hard to find that spark of inspiration. In this exercise, you'll use folder and file processing to create a web service that takes in a single parameter name and outputs a randomly-generated recipe idea based on the letters in the name.

The first letter of the name will correspond to the first letter of the recipe name, and the rest of the letters of the name will each correspond to a randomly-generated ingredient for that recipe. The final recipe will be output in plain text.

Go "down" to work through the slides specific to this exercise.

Exercise 2: Provided Files

Download and unzip the provided recipe-generator.zip. In this folder, there are two sub-directories, foods and ingredients. Each contains exactly 26 txt files each corresponding to a lower-cased letter of the English alphabet. foods includes txt files listing recipe names, and ingredients includes txt files include ingredients.

There is also a starting recipe-generator.php provided with comments for you to fill in as well as two helper functions. In this exercise, you will work through the steps outlined in those comments, but there are more details in the slides below.

Exercise 2: Example file in foods


Macaroni
Manicotti
Mantou
Marmalade
Masala
Milkshake
Minestrone Soup
Mochi
Mooncake
Muffin

foods/m.txt contents

Exercise 2: Example file in ingredients


1 cup rolled Oats
1 Okra
1 Olive
1 Orange
1 tsp Oregano
1 Oreo
1 box of Oreos
1 cup Onions
1 cup Orzo
1 Oyster

ingredients/o.txt contents

Note that each ingredient file contains a list of possible ingredients starting with the corresponding letter. To help generate a "reasonable" recipe, each ingredient option includes a unit (e.g. for the ingredient "Oreo" in ingredients/o.txt, it may be listed as "1 Oreo", but there is also an option for "1 box of Oreos" for extra oreo-ness in your recipe). Your code should not depend on this format though, it just makes the output nicer.

Exercise 2: Example Output

The following is an example (random) output for a request to
recipe-generator.php?name=Mowgli:


Mowgli's Muffin
Directions:
In a bowl, mix:
  1 Oreo
  1 gallon of Water
  1 oz of Green peas
  1 Lentil
  1 Ice cube

Cook for 6 minutes and serve!
              

Example output (plain text)

Exercise 2: Generating and Outputting the Recipe

Now that we know the format of the files we have in the two directories, let's implement the recipe generator. Make sure to write your recipe-generator.php file in the same directory as the two subdirectories. Follow the steps in the provided starting code to finish the web service. Hints and expected behavior of each step are included below (remaining steps included in the following two slides below).

In Step 1, check for a valid GET parameter, otherwise output a 400 error with a helpful error message of your choice.

In Step 2, set the appropriate header type for the web service before printing your output. This is used to specify what type of data your web service is outputting (in this case, plain text).

Exercise 2: Steps 3 and 4

In Step 3, generate a random recipe name based on the first letter of the passed name and print the recipe name as the first line. Remember that you'll only need to use foods/ in this step (the recipe name should be in the format of <name>'s <foodname>, replace <name> with the value of the passed parameter, and <foodname> with the randomly-chosen food name).

In Step 4, output the intermediate text, "Directions:" and "In a bowl, mix:", each on new lines, to introduce the ingredients. We have provided a helper function for you for you to use.

Exercise 2: Steps 5 and 6

In Step 5, generate a list of randomly-chosen ingredients based on the rest of the letters in the name. Each ingredient should be printed on a new line, prepending " " (two spaces) for indentation. Remember that you'll only need to use ingredients/ in this step.

In Step 6, output the final line of the recipe, indicating how long to cook before serving. A recipe should be listed to cook for X minutes, where X is the length of the name parameter.

Exercise 2: Solution

Source (PHP)

Running Version (PHP)