Tuesday, June 7, 2011

PHP Lesson 4: I/O - Input and Output

As I mentioned in lesson 1, we are using PHP from the command line for interactive programming. This is different from the typical usage of PHP as a tool for helping to build websites. So far, we've managed to do output to the screen by using the echo command, but in order to make a truly interactive program we also need to handle input. Since PHP is normally running as a back-end tool on a web server, it isn't ready to receive input from the keyboard until you tell it that's what you're going to be doing. We do this by opening the "standard input" as if it were a file. Standard Input, or stdin is programming jargon for the "keyboard," in most cases. It takes one line of code to open the standard input and store it in a file handle called $stdin:
$stdin = fopen("php://stdin", "r");
A file handle is a variable, just like the other variables we used to store numbers and strings in previous lessons, except that it stores detailed information about a file which is opened for reading or writing or both. In this case, we opened the file for reading by passing a string containing the "r" flag. (A flag in this case is just a single letter code which has a special meaning to the fopen function.)

Wait... what is that fopen thing? It is a function. We will learn more about functions in a later lesson, but for now I will just say this. Remember the f(x) notation from math class in school? You probably used it in trigonometry to talk about sine and cosine, sin(x) and cos(y), and so forth. For now, it will suffice to think of a function as an operation with a name that takes input (given in parenthesis) and returns an item of output... in the case of fopen, the name of the function is fopen, the input is the filename and the mode, and the output is a file handle.

What does php://stdin mean, anyway?

Ok, so that doesn't exactly look like a filename, does it? In most places where PHP can accept a filename, it can also accept a URL. (Actually a URI, but that's another lecture!) PHP is very web-oriented and modern in this sense. If a filename has been specified which does not include a protocol specifier (the part before the ://) it is assumed to be a local file, and the local file system's protocol specifier is presumed (file://). The most common protocol specifiers aside from this are http:// and https:// which allow the reading from a webpage (either plainly transmitted or using the secure socket layer technology) as if it were a file. You may use these quite frequently in the future. But, in addition to these, PHP also has its own built-in "fake protocol" in order to allow access to input, output, and a few other things, and it uses the fake protocol specifier "php://" to accomplish this feat.

So to sum up, what it really means is this: We want to open stdin, but we know it isn't actually a file on disk, and it isn't a website either ... in our case, it's the keyboard ... so we have to put php:// in front of it so PHP knows that we're asking for its special stdin file handle which reads typed characters from the keyboard, and that we're not asking for some other thing on the system which happens to be named stdin.

Side Bar: The Relation between Operators and Functions

I want to point out that operators are really just a form of "syntactic sugar" and everything could actually be done in function notation if the language designers had chosen to do so.

For example:
x = a + b * c
The above expression could be re-written as shown below, had the language designers chosen to provide function notation in lieu of operators:
assign(x, add(a, mul(b, c)))
In reality, these particular functions (assign, add, and mul) are not built into PHP, because the designers of PHP have instead chosen to provide operators, but what I'm trying to get across here is the idea that operator notation and function notation serve the same purpose. They perform an operation and return a result.

The number of operators available is limited, because they are based on the symbols on the keyboard. In PHP these operators are usually one or two symbols in length (+, -, *, /, ^, =, &&, <=, etc.) Functions, on the other hand, are virtually unlimited in number, because they can have a lengthy name, following the same naming rules as an identifier for a variable (except that function names are not prefixed with a dollar sign as variable names are.) In fact, there are thousands of functions predefined in PHP, and you can also define your own custom functions—a topic we will explore in our next lesson.

Reading from Standard Input

So we've used fopen to open a "file" named "php://stdin" in read-only mode, and we've stored the handle to this file in a new variable called $stdin. What can we do with it now?

There is a function called fgets which reads a string from a file. It will read until a newline character (or an end-of-file marker) is reached. When used in conjunction with the keyboard via stdin, it will read until the user presses the Enter key, and the value read will include the newline character created by the Enter key itself.

Example of usage:
$s = fgets($stdin);
In this example, a line of text is read from the $stdin file, and is saved into the variable called $s.

Now, lets print out the value we read so we can see what it looked like:
echo "I believe " . $s . " is what you typed.";
So your program, excluding the magic PHP tag at the top, should now look something like this:

$stdin = fopen("php://stdin", "r");
echo "Please type something: ";
$s = fgets($stdin);
echo "I believe " . $s . " is what you typed.";

I've added an additional echo statement before the fgets to provide a prompt. If I didn't do this, the program would run and just sit there with a blinking cursor, so I figured it would be good to clue the user of the program in on what we're expecting.

Go ahead and run it and see what happens. I will enter "something" as my text. I'm going to show the entire console output I get beginning from the prompt where I ran the program:

jeffd@mercury:~/programming$ php something.php
Please type something: something
I believe something
is what you typed.jeffd@mercury:~/programming:$

So, it did exactly what we asked it to, but we have two basic problems with our program, both of which have to do with the location of newline codes. First, you will see that our output, which should have been "I believe something is what you typed." ended up having a newline in it. This is because fgets, when reading input from the user, includes the newline which marked the end of the input as part of the result. We can fix this by using the trim() function which removes all whitespace from the beginning and end of a string. This will remove any and all spaces, tab characters, and carriage return or newline characters from the beginning or the end of the string. Our modified line of code for receiving the input looks like this:
$s = trim(fgets($stdin));
The next problem is that when our program ended, the command prompt was printed onto the same line as the final line of our output. This doesn't look good, so to fix it, we should output a carriage return and new line sequence at the end of the program by adding this line of code at the end of the program:
echo "\r\n";
Our completed program code looks like this:
$stdin = fopen("php://stdin", "r");
echo "Please type something: ";
$s = trim(fgets($stdin));
echo "I believe " . $s . " is what you typed.";
echo "\r\n";
And the program output now looks like this:
jeffd@mercury:~/programming$ php something.php
Please type something: something
I believe something is what you typed.
jeffd@mercury:~/programming$
Ok, that looks great! So in this lesson we've successfully done Input and Output. I want to bring to your attention that with the knowledge you now have you may begin to use the if statement from the previous lesson to branch your program (cause it to do different things) depending on the text which was entered by the user. Remember, when comparing text, PHP is case sensitive. So, for example, if you ask a yes or no question, a response of "Y" is different than a response of "y", and both cases need to be taken into account if you wish your program to work as expected.

Some Tips About Prompts

I would suggest that whenever your program is awaiting input from the user, it should first output a clear question followed by either "? " or ": ", that is a question mark or a colon followed by a space. If you are asking a multiple choice question, you may list the valid answers in brackets (or in parenthesis, but stay consistent) before the colon, such as this:
Do you want to continue? [Y/n]: _
I have placed an underscore above to indicate where the cursor would be blinking while awaiting the user's input. If you have a default choice which will take effect of the user presses enter without answering, it should be shown in uppercase, as the Y is in the above prompt example, in the case of a fill-in-the-blank type of answer, indicate the default in brackets or parenthesis, like so:
In what directory do you wish to save your files? [/home/jeffd]: _
Well, that's it for this time!

Next Post: Functions