Working with Files and Directories - Getting Involved with the Code - Sams Teach Yourself PHP, MySQL and Apache All in One (2012)

Sams Teach Yourself PHP, MySQL and Apache All in One (2012)

Part III. Getting Involved with the Code

Chapter 13. Working with Files and Directories


In this chapter, you learn the following:

How to include other files in your documents

How to test for the existence of files and directories

How to open a file before working with it

How to read data from files

How to write or append to a file

How to lock a file

How to work with directories

How to pipe data to and from external applications

How to send shell commands and display results in the browser


Testing for the existence of files, reading from them, and writing to them are important features of any rich programming language. PHP is no exception, in that it provides functions that make these processes straightforward. In addition, because PHP, Apache, and MySQL are not the only applications on your machine, you might sometimes need to access those other applications from within your PHP code, something you are shown how to do in this chapter.

Including Files

The include statement enables you to incorporate other files (usually other PHP scripts) into your PHP documents. PHP code in these included files will then be executed as if it were part of the main document. This is useful for including code libraries within multiple pages.

Suppose, for instance, you create a really useful function. Without the include statement, your only option is to paste your new function into every document that needs to use it. Of course, if you discover a bug or want to add a feature, you have to find every page you pasted the function into and make the change—over and over again. The include statement saves you from such a chore. You can add your newly created function to a single document, such as myinclude.php and, at runtime, read it into any page that needs it.

The include statement requires a single argument: a relative path to the file to include. Listing 13.1 creates a simple PHP script that uses include to incorporate and output the contents of a file.

Listing 13.1 Using include()


1: <?php
2: include 'myinclude.php';
3: ?>


The include statement in Listing 13.1 incorporates the document myinclude.php, the contents of which you can see in Listing 13.2.

Listing 13.2 The File Included in Listing 13.1


1: I have been included!!


Put the contents of Listing 13.1 in a file named test_include.php, and the contents of Listing 13.2 in a file named myinclude.php. Place both files in your web server document root. When you access test_include.php through your web browser, the output on the screen is as follows:

I have been included!!

This might seem strange to you, given that you’re including plaintext within a block of PHP code. In fact, the contents of an included file display as text by default. If you want to execute PHP code in an included file, you must enclose it in PHP start and end tags. Listing 13.3 amends the contents of myinclude.php so that code is executed in the included file.

Listing 13.3 An Include File Containing PHP Code


1: <?php
2: echo "I have been included!!<br/>";
3: echo "But now I can add up... 4 + 4 = ".(4 + 4);
4: ?>


Put the contents of Listing 13.3 in a file named myinclude2.php and change the value of the included file in test_include.php to point to this new file. Place both these files in your web server document root. Now, when you access test_include.php through your web browser, the output on the screen is as follows:

I have been included!!
But now I can add up... 4 + 4 = 8

The only way you would see the number 8 is if the code for adding 4 and 4 were executed as PHP, and it was.

Returning a Value from an Included Document

Included files in PHP can return a value in the same way that functions do. As in a function, using the return statement ends the execution of code within the included file. In addition, no further HTML is included. Listings 13.4 and 13.5 include a file and assign its return value to a variable.

Listing 13.4 Using include to Execute PHP and Assign the Return Value


1: <?php
2: $addResult = include 'returnvalue.php';
3: echo "The include file returned ".$addResult;
4: ?>


Listing 13.5 An Include File That Returns a Value


1: <?php
2: $retval = ( 4 + 4 );
3: return $retval;
4: ?>
5: This HTML will never be displayed because it comes after a return statement!


Put the contents of Listing 13.4 in a file named test_returnvalue.php; put the contents of Listing 13.5 in a file named returnvalue.php. Place both of these files in your web server document root. When you access test_returnvalue.php through your web browser, the output is this:

The include file returned 8.

Just as the string suggests on line 5 of Listing 13.5, anything outside the PHP block will not be displayed if there is a PHP block present in the included file.

Using include Within Control Structures

If you use an include statement within a conditional statement, it is treated like any other code—the file is included only if the condition is met. For example, the include statement in the following fragment will never be called:

$test = "1";
if ($test == "2") {
include 'file.txt'; // won't be included
}

If you use an include statement within a loop, the file will literally be included each time the loop iterates. Listing 13.6 illustrates this concept by using an include statement in a for loop. The include statement references a different file for each iteration.

Listing 13.6 Using include Within a Loop


1: <?php
2: for ($x = 1; $x<=3; $x++) {
3: $incfile = "incfile".$x.".txt";
4: echo "Attempting to include ".$incfile."<br/>";
5: include $incfile;
6: echo "<hr/>";
7: }
8: ?>


Save the contents of Listing 13.6 in a file named loopy_include.php and place it in the document root of the web server, along with three different files: incfile1.txt, incfile2.txt, and incfile3.txt. If we assume that each of these files simply contains a confirmation of its own name, the output should look like Figure 13.1.

image

Figure 13.1 Output of loopy_include.php.

Although this code worked fine, using the include function in this manner is sometimes not such a great idea, as discussed in the next subsection.

Using include_once

One problem caused by using several different libraries of code within your code is the danger of calling include twice on the same file. This sometimes happens in larger projects when different library files call include on a common file. Including the same file twice often results in repeated declarations of functions and classes, thereby causing the PHP engine great unhappiness.

This situation is remedied by using the include_once statement in place of the include statement. The include_once statement requires the path to an include file and otherwise behaves the same way as the include statement the first time it is called. However, if include_once is called again for the same file during script execution, the file will not be included again. This makes include_once an excellent tool for the creation of reusable code libraries!

The include_path Directive

Using include and include_once to access your code libraries can increase the flexibility and reusability of your projects. However, there are still headaches to overcome. Portability, in particular, can suffer if you hard-code the paths to included files. Imagine that you create a lib directory and reference it throughout your project:

include_once '/home/user/bob/htdocs/project4/lib/mylib.inc.php';

When you move your project to a new server, you might find that you have to change a hundred or more include paths, if this is hard-coded in a hundred or more files. You can escape this fate by setting the include_path directive in your php.ini file:

include_path .:/home/user/bob/htdocs/project4/lib/

The include_path value can include as many directories as you want, separated by colons (semicolons in Windows). The order of the items in the include_path directive determines the order in which the directories are searched for the named file. The first dot (.) before the first colon indicates “current directory,” and should be present. Now any path used in include or include_once can be relative to the value of include_path:

include_once 'mylib.inc.php';

When you move your project, you need to change only the include_path directive.


Note

PHP also has a require statement, which performs a similar function to include, and a require_once statement that works similarly to include_once.

Anything pulled into your code by require is executed regardless of a script’s flow, and therefore shouldn’t be used as part of conditional or loop structures.

Also, be aware that a file included as the result of a require statement cannot return a value.


Validating Files

Before you work with a file or directory within your code, it is often a good idea to learn more about it, and determining whether it actually exists is a pretty good start! PHP provides many functions to help you to discover information about files on your system. This section briefly covers some of the most useful functions.

Checking for Existence with file_exists()

You can test for the existence of a file with the file_exists() function. This function requires a string representation of an absolute or relative path to a file, which might or might not be present. If the file is found, the file_exists() function returns true; otherwise, it returns false:

if (file_exists('test.txt')) {
echo "The file exists!";
}

This is all well and good, but what if you’re unsure whether something is a file or a directory, and you really need to know? Read on.

A File or a Directory?

You can confirm that the entity you’re testing is a file, as opposed to a directory, using the is_file() function. The is_file() function requires the file path and returns a Boolean value:

if (is_file('test.txt')) {
echo "test.txt is a file!";
}

Conversely, you might want to check that the entity you’re testing is a directory. You can do this with the is_dir() function. is_dir() requires the path to the directory and returns a Boolean value:

if (is_dir('/tmp')) {
echo "/tmp is a directory";
}

After you know a file or directory exists, you might need to test its permissions. You learn about this in the next section.

Checking the Status of a File

When you know that a particular entity exists, and it’s what you expect it to be (either a directory or a file), you need to know what you can do with it. Typically, you might want to read, write to, or execute a file. PHP can help you determine whether you can perform these operations.

The is_readable() function tells you whether you can read a file. On UNIX systems, you might be able to see a file but still be barred from reading its contents because of its user permissions. The is_readable() function accepts the file path as a string and returns a Boolean value:

if (is_readable('test.txt')) {
echo "test.txt is readable";
}

The is_writable() function tells you whether you have the proper permission to write to a file. As with is_readable(), the is_writable() function requires the file path and returns a Boolean value.

if (is_writable('test.txt')) {
echo "test.txt is writable";
}

The is_executable() function tells you whether you can execute the given file, relying on either the file’s permissions or its extension, depending on your platform. The function accepts the file path and returns a Boolean value.

if (is_executable('test.txt')) {
echo "test.txt is executable";
}

Permission-related information isn’t all you might need to know about a file. The next section shows how to determine the file size.

Determining File Size with filesize()

Given the path to a file, the filesize() function attempts to determine and return its size in bytes. It returns false if it encounters problems:

echo "The size of test.txt is ".filesize('test.txt');

Finding the specific file size is important in situations where you want to attach a file to an email or stream a file to the user—you need to know the size so as to properly create the headers (in the case of the email) or know when to stop sending bytes to the user (in the case of the stream). For more general purposes, you might want to get the file size so that you can display it to the user before she attempts to download some monstrous application or high-resolution photograph from your site.

Getting Date Information About a File

Sometimes you need to know when a file was last written to or accessed. PHP provides several functions that can provide this information.

You can find out the last-accessed time of a file using the fileatime() function. This function requires the file path and returns the date that the file was last accessed. To access a file means either to read or write to it. Dates are returned from all the date information functions in timestamp—that is, the number of seconds since January 1, 1970. The examples in this book use the date() function to translate this value into human-readable form:

$atime = fileatime("test.txt");
echo "test.txt was last accessed on ".date("D d M Y g:i A", $atime);
// Sample output: test.txt was last changed on Sat 26 Apr 2008 12:52 PM

You can discover the modification date of a file with the function filemtime(), which requires the file path and returns the date in UNIX epoch format. To modify a file means to change its contents in some way:

$mtime = filemtime("test.txt");
echo "test.txt was last modified on ".date("D d M Y g:i A", $mtime);
// Sample output: test.txt was last modified on Wed 18 Jan 2012 7:11 PM

PHP also enables you to test the change time of a document with the filectime() function. On UNIX systems, the change time is set when a file’s contents are modified or when changes are made to its permissions or ownership. On other platforms, the filectime() returns the creation date:

mtime = filemtime("test.txt");
echo "test.txt was last modified on ".date("D d M Y g:i A", $mtime);
// Sample output: test.txt was last modified on Wed 18 Jan 2012 7:11 PM

Creating a Function That Performs Multiple File Tests

Listing 13.7 creates a function that brings together the file-related functions just discussed into one script.

Listing 13.7 A Function to Output the Results of Multiple File Tests


1: <?php
2: function outputFileTestInfo($f) {
3: if (!file_exists($f)) {
4: echo "<p>$f does not exist</p>";
5: return;
6: }
7: echo "<p>$f is ".(is_file($f) ? "" : "not ")."a file</p>";
8: echo "<p>$f is ".(is_dir($f) ? "" : "not ")."a directory</p>";
9: echo "<p>$f is ".(is_readable($f) ? "": "not ")."readable</p>";
10: echo "<p>$f is ".(is_writable($f) ? "": "not ")."writable</p>";
11: echo "<p>$f is ".(is_executable($f) ? "": "not ")."executable</p>";
12: echo "<p>$f is ".(filesize($f))." bytes</p>";
13: echo "<p>$f was accessed on ".date( "D d M Y g:i A",fileatime($f))."</p>";
14: echo "<p>$f was modified on ".date( "D d M Y g:i A",filemtime($f))."</p>";
15: echo "<p>$f was changed on ".date( "D d M Y g:i A",filectime($f) )."</p>";
16: }
17: $file = "test.txt";
18: outputFileTestInfo($file);
19: ?>


If you save this code to the document root of your web server as filetests.php and run through your web browser, the output would look something like Figure 13.2 (provided you have a file named test.txt also in the document root).

image

Figure 13.2 Output of filetests.php.

Notice that the ternary operator is used as a compact way of working with some of these tests. Let’s look at one such test, found in line 7, in more detail:

echo "<p>$f is ".(is_file($f) ? "" : "not ")."a file</p>";

The is_file() function is used as the left-side expression of the ternary operator. If it returns true, an empty string is returned. Otherwise, the string "not " is returned. The return value of the ternary expression is added to the string to be printed with concatenation operators. This statement could be made clearer, but less compact, as follows:

$is_it = is_file($f) ? "" : "not ";
echo "<p>".$f." is ".$is_it." a file</p>";

You could, of course, be even clearer with an if statement, but imagine how large the function would become if you used the following:

if (is_file($f)) {
echo "<p>$f is a file</p>";
} else {
echo "<p>$f is not a file</p>";
}

Because the result of these three approaches is the same, the approach you take becomes a matter of preference.

Creating and Deleting Files

If a file does not yet exist, you can create it with the touch() function. Given a string representing a file path, touch() attempts to create an empty file of that name. If the file already exists, its contents is not disturbed, but the modification date is updated to reflect the time at which the function executed:

touch('myfile.txt');

You can remove an existing file with the unlink() function. As did the touch() function, unlink() accepts a file path:

unlink('myfile.txt');

All functions that create, delete, read, write, and modify files on UNIX systems require the correct file or directory permissions to be set.

Opening a File for Writing, Reading, or Appending

Before you can work with a file, you must first open it for reading or writing, or for performing both tasks. PHP provides the fopen() function for doing so, and this function requires a string that contains the file path, followed by a string that contains the mode in which the file is to be opened. The most common modes are read (r), write (w), and append (a).

The fopen() function returns a file resource you use later to work with the open file. To open a file for reading, you use the following:

$fp = fopen("test.txt", "r");

You use the following to open a file for writing:

$fp = fopen("test.txt", "w");

To open a file for appending (that is, to add data to the end of a file), you use this:

$fp = fopen("test.txt", "a");

The fopen() function returns false if the file cannot be opened for any reason. Therefore, it’s a good idea to test the function’s return value before proceeding to work with it. You can do so with an if statement:

if ($fp = fopen("test.txt", "w")) {
// do something with the $fp resource
}

Or, you can use a logical operator to end execution if an essential file can’t be opened:

($fp = fopen("test.txt", "w")) or die("Couldn't open file, sorry");

If the fopen() function returns true, the rest of the expression is not parsed, and the die() function (which writes a message to the browser and ends the script) is never reached. Otherwise, the right side of the or operator is parsed, and the die() function is called.

Assuming that all is well and you go on to work with your open file, you should remember to close it when you finish. You can do so by calling fclose(), which requires the file resource returned from a successful fopen() call as its argument:

fclose($fp);

The resource that became available ($fp) is now unavailable to you.

Reading from Files

PHP provides a number of functions for reading data from files. These functions enable you to read by the byte, by the whole line, and even by the single character.

Reading Lines from a File with fgets() and feof()

When you open a file for reading, you might want to access it line by line. To read a line from an open file, you can use the fgets() function, which requires the file resource returned from fopen() as its argument. You must also pass fgets() an integer as a second argument, which specifies the number of bytes that the function should read if it doesn’t first encounter a line end or the end of the file. The fgets() function reads the file until it reaches a newline character ("\n"), the number of bytes specified in the length argument, or the end of the file—whichever comes first:

$line = fgets($fp, 1024); // where $fp is the file resource returned by fopen()

Although you can read lines with fgets(), you need some way to tell when you reach the end of the file. The feof() function does this by returning true when the end of the file has been reached and false otherwise. The feof() function requires a file resource as its argument:

feof($fp); // where $fp is the file resource returned by fopen()

You now have enough information to read a file line by line, as shown in Listing 13.8.

Listing 13.8 Opening and Reading a File Line by Line


1: <?php
2: $filename = "test.txt";
3: $fp = fopen($filename, "r") or die("Couldn't open $filename");
4: while (!feof($fp)) {
5: $line = fgets($fp, 1024);
6: echo $line."<br/>";
7: }
8: ?>


If this code were saved to the document root of your web server as readlines.php and run through your web browser, the output would look something like Figure 13.3 (although the contents of your text file might differ).

image

Figure 13.3 Output of readlines.php.

The code calls fopen() on line 3, using the name of the file that you want to read as its argument. The or die() construct is used to ensure that script execution ends if the file cannot be read. This usually occurs if the file does not exist or if the file’s permissions do not allow read access to the file.

The actual reading of the file contents takes place in the while statement on line 4. The while statement’s test expression calls feof() for each iteration, ending the loop when it returns true. In other words, the loop continues until it reaches the end of the file. Within the code block, fgets() on line 5 extracts a line (or 1024 bytes, whichever comes first) from the file. The result is assigned to $line and printed to the browser on line 6, appending a <br/> tag for the sake of readability.

Reading Arbitrary Amounts of Data from a File with fread()

Rather than reading text by the line, you can choose to read a file in arbitrarily defined chunks. The fread() function accepts a file resource as an argument, as well as the number of bytes you want to read. The fread() function returns the amount of data you requested, unless the end of the file is reached first:

$chunk = fread($fp, 16);

Listing 13.9 amends the previous example so that it reads data in chunks of 8 bytes rather than by the line.

Listing 13.9 Reading a File with fread()


1: <?php
2: $filename = "test.txt";
3: $fp = fopen($filename, "r") or die("Couldn't open $filename");
4: while (!feof($fp)) {
5: $chunk = fread($fp, 8);
6: echo $chunk."<br/>";
7: }
8: ?>


If this code were saved to the document root of your web server as readlines2,php and run through your web browser, the output would look something like Figure 13.4.

image

Figure 13.4 Output of readlines2.php.

Although the fread() function enables you to define the amount of data acquired from a file, it doesn’t let you decide the position from which the acquisition begins. You can set this manually with the fseek() function.

The fseek() function lets you to change your current position within a file. It requires a file resource and an integer that represents the offset from the start of the file (in bytes) to which you want to jump:

fseek($fp, 64);

Listing 13.10 uses fseek() and fread() to output the second half of a file to the browser.

Listing 13.10 Moving Around a File with fseek()


1: <?php
2: $filename = "test.txt";
3: $fp = fopen($filename, "r") or die("Couldn't open $filename");
4: $fsize = filesize($filename);
5: $halfway = (int)($fsize / 2);
6: echo "Halfway point: ".$halfway." <br/>\n";
7: fseek($fp, $halfway);
8: $chunk = fread($fp, ($fsize - $halfway));
9: echo $chunk;
10: ?>


If this code were saved to the document root of your web server as readseek.php and run through your web browser, the output would look something like Figure 13.5.

image

Figure 13.5 Output of readseek.php.

The code calculates the halfway point of our file on line 5, by dividing the return value of filesize() by 2. It uses this as the second argument to fseek() on line 7, jumping to the halfway point of the text file. Finally, on line 8, fread() is called to extract the second half of the file and then the result is printed to the browser.

Reading Characters from a File with fgetc()

The fgetc() function is similar to fgets() except that it returns only a single character from a file every time it is called. Because a character is always 1 byte in size, fgetc() doesn’t require a length argument. You must simply pass it a file resource:

$char = fgetc($fp);

Listing 13.11 creates a loop that reads the file test.txt one character at a time, outputting each character to the browser on its own line.

Listing 13.11 Moving Around a File with fgetc()


1: <?php
2: $filename = "test.txt";
3: $fp = fopen($filename, "r") or die("Couldn't open $filename");
4: while (!feof($fp)) {
5: $char = fgetc($fp);
6: echo $char."<br/>";
7: }
8: ?>


If this code were saved to the document root of your web server as readchars.php and run through your web browser, the output would look something like Figure 13.6.

image

Figure 13.6 Output of readchars.php.

Reading File Contents with file_get_contents()

Now that you get the point of creating a file resource with fopen() and performing one or more actions on that resource, you can dispense with those pleasantries and get right to the point by using file_get_contents() to read an entire file into a string. For example, the following single line of code reads the contents of a file called test.txt into a variable called $contents:

$contents = file_get_contents("test.txt");

This function affords you some other possibilities as well; the following shows allowable options when calling file_get_contents():

file_get_contents ($filename [$use_include_path [, $context [, $offset [,
$maxlen ]]]]);

These additional options are described as such:

use_include_path—a Boolean value that indicates whether or not the function should search the entire include_path for the filename.

context—a context resource created by stream_context_create(); use NULL if not needed.

offset—the point at which reading should start, such as 5 for the fifth character in the file. Note that while you can use file_get_contents() with non-local files (such as URLs), offset cannot be used in those situations.

maxlen—the maximum length of data to read, in bytes. The default is to read all of the data.

If the fine-grained controls of the previous functions are not necessary in your scripts, then file_get_contents() will serve many of your basic file reading purposes quite well.

Writing or Appending to a File

The processes for writing to and appending to a file are the same—the difference lies in the mode with which you call the fopen() function. When you write to a file, you use the mode argument "w" when you call fopen():

$fp = fopen("test.txt", "w");

All subsequent writing occurs from the start of the file. If the file doesn’t already exist, it is created. If the file already exists, any prior content is destroyed and replaced by the data you write.

When you append to a file, you use the mode argument "a" in your fopen() call:

$fp = fopen("test.txt", "a");

Any subsequent writes to your file are added to the end of your existing content, but if you attempt to append content to a nonexistent file, the file is created first.

Writing to a File with fwrite() or fputs()

The fwrite() function accepts a file resource and a string, and then writes the string to the file. The fputs() function works in exactly the same way:

fwrite($fp, "hello world");
fputs($fp, "hello world");

Writing to files is as straightforward as that. Listing 13.12 uses fwrite() to print to a file. It then appends an additional string to the same file using fputs().

Listing 13.12 Writing and Appending to a File


1: <?php
2: $filename = "test.txt";
3: echo "<p>Writing to ".$filename." ... </p>";
4: $fp = fopen($filename, "w") or die("Couldn't open $filename");
5: fwrite($fp, "Hello world\n");
6: fclose($fp);
7: echo "<p>Appending to ".$filename." ...</p>";
8: $fp = fopen($filename, "a") or die("Couldn't open $filename");
9: fputs($fp, "And another thing\n");
10: fclose($fp);
11: ?>


The screen output of this script, when run from your web browser, is as follows:

Writing to test.txt ...
Appending to test.txt ...

If you open the test.txt file or use readlines.php to read its contents, you find the file now contains the following:

Hello world
And another thing

Writing File Contents with file_put_contents()

Much like the file_get_contents() function discussed previously, the file_put_contents() function is a more streamlined approach to file operations. Specifically, file_put_contents() directly mimics the process of calling fopen(), fwrite(), and fclose() in succession, to write data to a file.

The following shows allowable parameters when calling file_put_contents():

file_put_contents ($filename, $data [, $flags [, $context]]);

These additional options are described as such:

data—a string or array that will contain the written data.

flags—one or more of FILE_USE_INCLUDE_PATH (whether or not to look for the target file in the include_path), FILE_APPEND (whether or not to append data to a file if the file already exists), and LOCK_EX (whether or not to acquire an exclusive lock on writing to the target file), which can be combined with the | operator.

context—a context resource created by stream_context_create(); use NULL if not needed.

If you were to rewrite Listing 13.12 using file_put_contents(), it would look like Listing 13.13:

Listing 13.13 Writing and Appending to a File


1: <?php
2: $filename = "test.txt";
3: echo "<p>Writing to ".$filename." ... </p>";
4: file_put_contents ($filename, "Hello world\n");
5: echo "<p>Appending to ".$filename." ...</p>";
6: file_put_contents ($filename, "And another thing\n", FILE_APPEND);
7: ?>


Locking Files with flock()

The techniques you just learned for reading and amending files work fine if only a single user is accessing your script. In the real world, however, you would expect many users to access your website, and the scripts within it, at more or less the same time. Imagine what would happen if two users were to execute a script that writes to one file at the same moment: The file would quickly become corrupt.

PHP provides the flock() function to forestall this eventuality. The flock() function locks a file to warn other processes against writing to or reading from that file while the current process is working with it. The flock() function requires a valid file resource from an open file and an integer representing the kind of lock you want to set. PHP provides predefined constants for each of the integers you’re likely to need. Table 13.1 lists three kinds of locks you can apply to a file.

Table 13.1 Integer Arguments to the flock() Function

image

You should call flock() directly after calling fopen(), and then call it again to release the lock before closing the file. If the lock is not released, you cannot to read from or write to the file. Here is an example of this sequence of events:

$fp = fopen("test.txt", "a") or die("Couldn't open file.");
flock($fp, LOCK_EX); // create exclusive lock
// write to the file
flock($fp, LOCK_UN); // release the lock
fclose($fp);


Tip

For more information on file locking, see the PHP Manual entry for the flock() function at http://www.php.net/flock.


Working with Directories

Now that you can test, read, and write to files, let’s turn our attention to directories. PHP provides many functions for working with directories. Let’s look at how to create, remove, and read from them.

Creating Directories with mkdir()

The mkdir() function enables you to create a directory. The mkdir() function requires a string that represents the path to the directory you want to create and an octal number integer that represents the mode you want to set for the directory. Remember, you specify an octal (base 8) number using a leading 0; for example, 0777 or 0400.

The mode argument has an effect only on UNIX systems. The mode should consist of three numbers between 0 and 7, representing permissions for the directory owner, group, and everyone, respectively. The mkdir() function returns true if it successfully creates a directory or false if it doesn’t. If mkdir() fails, it is usually because the containing directory has permissions that preclude processes with the script’s user ID from writing.

If you’re not comfortable setting UNIX directory permissions, you should find that one of the following examples fits your needs. Unless you really need your directory to be world-writable, you should probably use 0755, which allows the world to read your directory but not to write to it:

mkdir("testdir", 0777); // global read/write/execute permissions
mkdir("testdir", 0755); // world/group: read/execute; owner: read/write/execute

Removing a Directory with rmdir()

The rmdir() function enables you to remove a directory from the filesystem if the process running your script has the right to do so, and if the directory is empty. The rmdir() function requires only a string representing the path to the directory you want to delete.

rmdir("testdir");

Opening a Directory for Reading with opendir()

Before you can read the contents of a directory, you must first obtain a directory resource. You can do so with the opendir() function. The opendir() function requires a string that represents the path to the directory you want to open. The opendir() function returns a directory handle unless the directory isn’t present or readable; in that case, it returns false:

$dh = opendir("testdir");

In this case, $dh is the directory handle of the open directory.

Reading the Contents of a Directory with readdir()

Just as you use the fgets() function to read a line from a file, you can use readdir() to read a file or directory name from a directory. The readdir() function requires a directory handle and returns a string containing the item name. If the end of the directory is reached, readdir() returnsfalse. Note that readdir() returns only the names of its items, rather than full paths. Listing 13.14 shows the contents of a directory.

Listing 13.14 Listing the Contents of a Directory with readdir()


1: <?php
2: $dirname = ".";
3 : $dh = opendir($dirname) or die("Couldn't open directory");
4:
5: while (!(($file = readdir($dh)) === false ) ) {
6: if (is_dir("$dirname/$file")) {
7: echo "(D) ";
8: }
9: echo $file."<br/>";
10: }
11: closedir($dh);
12: ?>


If this code were saved to the document root of your web server as readdir.php and run through your web browser, the output would look something like Figure 13.7.

image

Figure 13.7 Output of readdir.php.

Listing 13.14 opens the directory for reading with the opendir() function on line 3 and uses a while statement to loop through each of its elements, beginning on line 5. It calls readdir() as part of the while statement’s test expression and assigns the result to the $file variable.

Within the body of the while statement, the $dirname variable is used in conjunction with the $file variable to create a full file path, which is then tested on line 6. If the path represents a directory, the code prints D to the browser on line 7. Finally, the filename (or directory name) is printed on line 9.

Listing 13.14 uses a cautious construction in the test of the while statement. Most PHP programmers (myself included) would use something like the following:

while ($file = readdir($dh)) {
echo $file."<br/>";
}

In this case, the value returned by readdir() is tested and, because any string other than "0" resolves to true, there should be no problem. Imagine, however, a directory that contains four files: 0, 1, 2, and 3. On my system, the output from the preceding code is as follows:

.
..

When the loop reaches the file named 0, the string returned by readdir() resolves to false, which causes the loop to end. The approach in Listing 13.14 uses === to check that the return value returned by readdir() is not exactly equivalent to false. The result 0 only resolves to false in the test, so the problem is circumvented.


Note

If you find the ordering of items in a directory listing to be arbitrary, it’s because the filesystem determines the order. If you want the items ordered in a specific fashion, you must read the contents into an array, which can then be sorted to your liking and subsequently displayed.


Opening Pipes to and from Processes Using popen()

Earlier in this chapter, you learned how to open a file for reading and writing using the fopen() function. Now, you see that you can open a pipe to a process using the popen() function.

The popen() function is used like this:

$file_pointer = popen("some command", mode)

The mode is either r (read) or w (write).

Listing 13.15 is designed to produce an error—it attempts to open a nonexistent file for reading.

Listing 13.15 Using popen() to Read a File


1: <?php
2: $file_handle = popen("/path/to/fakefile 2>&1", "r");
3: $read = fread($file_handle, 2096);
4: echo $read;
5: pclose($file_handle);
6: ?>


Listing 13.15 first calls the popen() function in line 2, attempting to open a file for reading. In line 3, any error message stored in the $file_handle pointer is read and printed to the screen in line 4. Finally, line 5 closes the file pointer opened in line 2.

If you save this code as test_popen.php, place it in your document root, and access it with your web browser, you will see an error message such as this one:

The system cannot find the path specified.

Listing 13.16 also uses popen() to read the output of a process; in this case, the output of the UNIX who command.

Listing 13.16 Using popen() to Read the Output of the UNIX who Command (UNIX Only)


1: <?php
2: $handle = popen("who", "r");
3: while (!feof($handle)) {
4: $line = fgets($handle,1024);
5: if (strlen($line) >= 1) {
6: echo $line."<br/>";
7: }
8: }
9: pclose($handle);
10: ?>


In line 2, a file pointer is returned when popen() is used for reading. Line 3 begins a while loop that reads each line of output from the process and eventually prints the line—if it contains information—in line 6. The connection is closed in line 9.

If you save this code as popen_who.php, place it in your document root, and access it with your web browser, you will see something like the following (with your actual information, not mine, of course):

julie pts/0 Mar 14 06:19 (adsl-63-206-120-158.dsl.snfc21.pacbell.net)

Listing 13.17 shows how to use popen() in write mode to pass data to an external application. The external application in this case is called column. The goal of the script is to take the elements of a multidimensional array and output them in table format, in an ASCII file.

Listing 13.17 Using popen() to Pass Data to the UNIX column Command (UNIX Only)


1: <?php
2: $products = array(
3: array("HAL 2000", 2, "red"),
4: array("Tricorder", 3, "blue"),
5: array("ORAC AI", 1, "pink"),
6: array("Sonic Screwdriver", 1, "orange")
7: );
8:
9: $handle = popen("column -tc 3 -s / > /somepath/purchases.txt", "w");
10: foreach ($products as $p) {
11: fputs($handle, join('/',$p)."\n");
12: }
13: pclose($handle);
14: echo "done";
15: ?>


Lines 2–7 define a multidimensional array called $products and in it place four entries representing products with names, quantities, and colors. In line 9, popen() is used in write format to send a command to the column application. This command sends parameters to the column application telling it to format the input as a three-column table, using / as a field delimiter. The output is sent to a file named purchases.txt. (Be sure to change the pathname to one that exists on your system.)

Lines 10–12 use foreach to loop through the $products array and send each element to the open file pointer. The join() function is then used to convert the arrays to a string, with the specified delimiter appended to it. The code closes the open file pointer in line 13, and in line 14 prints a status message to the screen.

If you save this code as popen_column.php, place it in your document root, and access it with your web browser, it should create a file in the specified location. Looking at the file created on my machine, I see the following text:

HAL 2000 2 red
Tricorder 3 blue
ORAC AI 1 pink
Sonic Screwdriver 1 orange

You may or may not have the column program on your system, but this section illustrated the logic and syntax for opening a pipe to an application. Feel free to try out this logic with other programs available to you.

Running Commands with exec()

The exec() function is one of several functions you can use to pass commands to the shell. The exec() function requires a string representing the path to the command you want to run, and optionally accepts an array variable that will contain the output of the command and a scalar variable that will contain the return value (1 or 0). For example:

exec("/path/to/somecommand", $output_array, $return_val);

Listing 13.18 uses the exec() function to produce a directory listing with the shell-based ls command.

Listing 13.18 Using exec() and ls to Produce a Directory Listing (UNIX Only)


1: <?php
2: exec("ls -al .", $output_array, $return_val);
3: echo "Returned ".$return_val."<br/><pre>";
4: foreach ($output_array as $o) {
5: echo $o."\n";
6: }
7: echo "</pre>";
8: ?>


Line 2 issues the ls command using the exec() function. The output of the command is placed into the $output_array array and the return value in the $return_val variable. Line 3 simply prints the return value, whereas the foreach loop in lines 4–6 prints out each element in $output_array.


Note

The string in line 3 includes an opening <pre> tag, and line 7 provides a closing tag. This simply ensures that your directory listing will be readable, using HTML preformatted text.


If you save this code as exec_ls.php, place it in your document root, and access it with your web browser, you will see something like Figure 13.8 (with your actual information, not mine, of course).

image

Figure 13.8 Output of exec_ls.php.

As wonderful as PHP is, you might sometime want to integrate some sort of functionality within your PHP-based application but someone else has already written code in Perl that does the same thing. In cases like this, there’s no need to reinvent the wheel; you can simply use exec() to access the existing script and utilize its functionality. However, remember that calling an external process always adds some amount of additional overhead to your script, in terms of both time and memory usage.

Running Commands with system() or passthru()

The system() function is similar to the exec() function in that it launches an external application, and it utilizes a scalar variable for storing a return value:

system("/path/to/somecommand", $return_val);

The system() function differs from exec() in that it outputs information directly to the browser, without programmatic intervention. The following snippet of code uses system() to print a man page for the man command, formatted with the <pre></pre> tag pair:

<?php
echo "<pre>";
system("man man | col –b", $return_val);
echo "</pre>";
?>

Similarly, the passthru() function follows the syntax of the system() function, but it behaves differently. When you are using passthru(), any output from the shell command is not buffered on its way back to you; this is suitable for running commands that produce binary data rather than simple text data. An example of this is to use shell tools to locate an image and send it back to the browser, as shown in Listing 13.19.

Listing 13.19 Using passthru() to Output Binary Data


1: <?php
2: if ((isset($_GET['imagename'])) && (file_exists($_GET['imagename']))) {
3: header("Content-type: image/gif");
4: passthru("giftopnm ".$_GET['imagename']." |
5: pnmscale -xscale .5 -yscale .5 | ppmtogif");
6: } else {
7: echo "The image ".$_GET['imagename']." could not be found";
8: }
9: ?>



Note

The shell utilities used in this script—giftopnm, pnmscale, and ppmtogif—might or might not be installed on your system. If they are not, you can probably install them from your OS distribution CD, but don’t worry about it just for this example. The point is just to use this listing to understand the concept of using the passthru() function.


If this file is named getbinary.php, it is called from HTML like this:

<img src="getbinary.php?imagename=<?php echo urlencode("test.gif") ?>">

In line 2 of Listing 13.19, the user input is tested to ensure that the file in question (test.gif, according to the HTML snippet) exists. Because the script outputs GIF data to the browser, the appropriate header is set on line 3.

On lines 4 and 5, the passthru() function consecutively executes three different commands (giftopnm, pnmscale, and ppmtogif), thus scaling the image to 50% of its original height and width. The output of the passthru() function—that is, the new image data—is sent to the browser.


Note

In this and other system-related examples, you could use escapeshellcmd() or escapeshellarg() function to escape elements in the user input. Doing so ensures that the user cannot trick the system into executing arbitrary commands such as deleting important system files or resetting passwords. These functions go around the first instance of the user input, such as the following:

$new_input = escapeshellcmd($_GET['someinput']);

You then reference $new_input throughout the remainder of your script, instead of $_GET['someinput']. Using these two commands, plus ensuring that your script is written so as to only perform tasks you want it to do, and not commands from your users, is a way to keep your system secure.


Summary

In this chapter, you learned how to use the include family of statements (include_once, require, and require_once thrown in for good measure) to incorporate files into your documents and to execute any PHP code contained in include files. You learned how to use some of PHP’s file-testing functions and explored functions for reading files by the line, by the character, and in arbitrary chunks. You learned how to write to files, by either replacing or appending to existing content, and you learned how to create, remove, and read directories.

You were also introduced to various methods of communicating with your system and its external applications. Although PHP is a fast and robust language, you might find it more cost- and time-effective to simply utilize preexisting scripts in other languages such as C or Perl. You can access these external applications using the popen(), exec(), system(), and passthru() functions.

You learned how to pipe data to a command using popen(), which is useful for applications that accept data from standard input and when you want to parse data as it is sent to you by an application. You also learned to use exec() and system() to pass commands to the shell and acquire user input. You also learned to use the passthru() function to accept binary data that is the result of a shell command.

Now that you can work with the filesystem a little more, you can save and access substantial amounts of data. If you need to look up data from large files, however, such scripts begin to slow down considerably. When that occurs, you should look into a database system, which will be coming your way shortly.

Q&A

Q. Does the include statement slow down my scripts?

A. Because an included file must be opened and parsed by the engine, it adds some overhead. However, the benefits of reusable code libraries often outweigh the relatively low performance overhead.

Q. Should I always end script execution if a file cannot be opened for writing or reading?

A. You should always allow for this possibility. If your script absolutely depends on the file you want to work with, you might want to use the die() function, writing an informative error message to the browser. In less-critical situations, you still need to allow for the failure, perhaps by adding it to a log file.

Q. Where can I get more information about security on the web?

A. One authoritative introduction to web security is “The World Wide Web Security FAQ” document written by Lincoln Stein, which you can find at http://www.w3.org/Security/Faq/.

Workshop

The workshop is designed to help you review what you’ve learned and begin putting your knowledge into practice.

Quiz

1. What functions do you use to add library code to the currently running script?

2. What function do you use to find out whether a file is present on your file-system?

3. How do you determine the size of a file?

4. What function do you use to open a file for reading or writing?

5. What function do you use to read a line of data from a file?

6. How can you tell when you’ve reached the end of a file?

7. What function do you use to write a line of data to a file?

8. How do you open a directory for reading?

9. What function do you use to read the name of a directory item after you’ve opened a directory for reading?

10. Which function do you use to open a pipe to a process?

11. How can you read data from a process after you have opened a connection? What about writing data?

12. How can you escape user input to make it a little safer before passing it to a shell command?

Answers

1. You can use the require or include statement to incorporate PHP files into the current document. You could also use include_once or require_once.

2. You can test for the existence of a file with the file_exists() function.

3. The filesize() function returns a file’s size in bytes.

4. The fopen() function opens a file. It accepts the path to a file and a character representing the mode. It returns a file resource.

5. The fgets() function reads data up to the buffer size you pass it, the end of the line, or the end of the document, whichever comes first.

6. The feof() function returns true when the file resource it has passed reaches the end of the file.

7. You can write data to a file with the fputs() function.

8. The opendir() function enables you to open a directory for reading.

9. The readdir() function returns the name of a directory item from an opened directory.

10. The popen() function is used to open a pipe to a process.

11. You can read and write to and from a process just as you can with an open file, namely with feof() and fgets() for reading and fputs() for writing.

12. If user input is part of your shell command, you can use the escapeshellcmd() or escapeshellarg() function to properly escape it.

Activities

1. Create a form that accepts a user’s first and second name. Create a script that saves this data to a file.

2. Create a script that reads the data file you created in the first activity. In addition to writing its contents to the browser (adding a <br/> tag to each line), print a summary that includes the number of lines in the file and the file’s size.