Search This Blog

Monday, April 26, 2010

Working with Files

PHP provides two sets of file-related functions, distinguished by the ways in which they handle files: some use a file handle, or a file pointer as it is commonly called; others use filename strings directly. The same is true of PHP's directory-related functions, for that matter.


A file handle is simply an integer value that is used to identify the file you want to work with until it is closed. If more than one file is opened, each file is identified by its own uniquely assigned handle.


A file handle is contained in a variable (named like any other PHP variable, and in examples typically $fp, for file handle). The integer value the file handle variable contains identifies the connection to the file you are working with. For example, once you open a file with fopen() you can use the fwrite() function to write data out to a file. The fwrite() function needs a file handle (named $fp in this case) that points to the file it is to work with:


Fopen($fp,"myfile.txt", "w+");
fwrite($fp, 'Hello world!');

In this example, $fp is the variable representing the file handle (the integer value it contains is set when you use fopen() to open the file specified in the first line). The fwrite() function writes the text contained in the second argument (second line) into the file represented by the file handle.


On the other hand, the file() function, which used to read data from a file, takes a string argument that holds the path to the file:


$lines = file('./data.txt');

These new functions are introduced shortly.
Unless otherwise specified, all the functions mentioned in this chapter return True when a given operation is successful and False upon error.
Are you ready to work with a file?

Opening and Closing Files

There are typically three steps involved in working with a file:
  1. Open the file you want to work with by associating a file handle with it.
  2. Read from or write to the file using the file handle.
  3. Close the file using the file handle.

The fopen() Function

The fopen() is used to open a file, returning a file handle associated with the opened file. It can take two or three arguments: filename, mode, and the optional use_include_path.


The file handle also can be used it to detect whether the file opened okay—if it did, the file handle is a positive integer; if not, the file handle is zero. Operations on files and directories are prone to errors, so you should always allow for things to go wrong when using them. It's good practice to use some form of error-checking procedure so that if an error occurs (perhaps you don't have necessary privileges to access the file, or the file doesn't even exist), your script will exit tidily, preferably with an appropriate error message. For example, because of the way PHP interprets integers in the context of Boolean operations, you can make use of the fact that the value of the file pointer resolves to True if the operation succeeds and False if it fails. This enables you to test to see if the file opened by using a script like this:


$fp = fopen("./data.txt", "r");
if(!$fp) die ("Cannot open the file");

Alternatively, you can write it like this:


if(!($fp = fopen("./data.txt", "r"))) die ("Cannot open the file");

This doesn't test to see if $fp is equal to the result of the fopen() function call (that would use the == equality operator), but simply tests whether the operation succeeded in opening the file. This is a shorthand trick to run the test and, if the operation succeeds, store the file handle in $fp for later use. Use the form with which you're most comfortable.


The first argument of the fopen() function specifies the name of the file you want to open, which can be just a filename, or a relative ("./data.inc", for example) or absolute ("/myfiles/data.inc") path to a file. (You'll see what happens if you simply specify the filename as data.txt when you look at the use_include_path argument a little later.) You can even specify a file on a remote host, opening it with an HTTP URL or via FTP, as these examples show:


if(!($fp = fopen("http://www.whatyoumaycallit.com/index.html", "r")))
         die ("Cannot open the file.");

if(!($fp = fopen("ftp://ftp.whatyoumaycallit.com/pub/index.txt", "r")))
         die ("Cannot open the file.");

A remote file can only be opened for reading—you can't modify or write to the file.


Important 
If you're not familiar with command-line file operations on either UNIX or Windows, you might be a little confused by the relative path notation. From the perspective of a file, a dot (.) refers to the directory the file is in, and two dots (.) refers to the immediate parent directory. For example, ./data.txt points to a file called data.txt in the same directory as the script, and ../data.txt points to a file called data.txt in the directory above the one containing the script. ../../../data.txt backs up the directory tree three levels before looking for the data. txt file. An absolute path is distinguished by the fact that it begins with a /, indicating that the path is specified relative to the root of the file system, not to the location of the script. For example, C:/Inetpub/wwwroot/index.php is an absolute path.


The second argument of the fopen() function (named mode) specifies how the open file is to be used. You can open a file for reading, writing, or appending to, by giving mode one of the following values:


Value
Description
r
Open, file for reading only. The file position indicator is placed at the beginning of the file
r+
Open file for reading and writing. The file position indicator is placed at the beginning of the file
w
Open file tor writing only. Any existing content will be lost. If the file does not exist, PHP attempts to create it
w+
Open file for reading and writing. Any existing file content will be lost. If the file does not exist, PHP attempts to create it
a
Open file for appending only. Data is written to the end of an existing file, If the file does not exist, PHP attempts to create it
a+
Open file for reading and appending. Data is written to the end of an existing file. If the file does not exist, PHP attempts to create it


The file position indicator is PHP's internal pointer that specifies the exact position in a file where the next operation should be performed.


The mode argument of the fopen() function can also take the value b to indicate that the opened file should be treated as a binary file. Although this is irrelevant for platforms such as Linux, which treats text and binary files identically, you may find it useful if you're running on Windows because Windows (and the Mac) have different ways of setting the end-of-line characters for text files (Windows uses CRLF, Mac uses CR, and Linux uses LF). The default mode of the fopen() function (since PHP 4.3.2) has been set to binary for all platforms that distinguish between text and binary files, meaning that no end-of-line character translation takes place. Using b as part of the mode argument is recommended, so your code should be written to apply or use the appropriate end-of-line characters for the platform on which you are running.


The third (optional) argument of the fopen() function is named use_include_path. If use_include_path is set to 1, and the filename isn't specified as a relative or absolute path, the function searches for the file specified by filename first in the script's own directory, and then in the directories defined by the variable include_path (set in the php.ini file).
The include path is especially useful if you want to specify a directory in which you put include files that are commonly accessed by your scripts. Here, for example, include_path has been given the value /home/apache/inc in the php.ini file, and the following function call is executed:


fopen("data.txt", "r", 1);

If this call now fails to find the file data.txt in the current directory, it will search in the directory /home/apache/inc.

The fclose() Function

Once you've finished working with a file, it needs to be closed. You can do this using fclose(), specifying the open file by using its associated file handle as a single argument, like this:


fclose ($fp)

Although PHP should close all open files automatically when your script terminates, it's good practice to close files from within your script as soon as you're finished with them because it frees them up quicker for use by other processes and scripts—or even by other requests to the same script.

Getting Information About a File

Files contain much information about themselves, such as their size, when they were modified, who owns them, and so forth. PHP includes the stat() function to enable you to capture information about a file by providing the filename as an argument to the function.
The stat() function returns an indexed array that contains file statistic and information within each spot in the array. There are 13 array spots:


Index Number
Associative Name
Information Contained
0
Dev
Device number
1
Ino
Inode number
2
Mode
Inode protection mode
3
nlink
Number of links
4
uid
Userid of owner
5
gid
Group id of owner
6
rdev
Device type, if inode device
7
size
Size in bytes
8
atime
Time of last access
9
mtime
Time of last modification
10
etime
Time of last change
11
blksize
Blocksize of file system 10
12
blocks
Number of blocks allocated


The following code snippet shows how to use the stat() function to retrieve the size of a file in bytes, assuming the file myfile.txt exists:


$my_file_stats = stat("myfile.txt");
$my_file_size = $my_file_stats[7];

Not every piece of information that can be retrieved with the stat() function is available on all operating systems. For example, if you're running your Web server and PHP on Windows 98 with the Personal Web server, there will be no group or user id available because those systems don't provide for such things. This subject is covered in more detail in the Ownership and Permissions section later in this chapter.

Reading and Writing to Files

Now take a look at a couple of the PHP functions you can use to read and write the data in file. Although they're quite simple, you can use together to implement a basic working script.

The fread() Function

The fread() function can be used to extract a character string from a file. It takes two arguments: a file handle fp and an integer length. The function reads up to length bytes from the file referenced by fp and return them as string. For example, you open a file with:


$fp = fopen("data.txt", "r")
And then say:
$data = fread($fp, 10);

fread() will read the first 10 bytes from data.txt and assign them to $data as characters in a string.


The function leaves the file position indicator 10 bytes lower down the file, so that if you repeat the call to fread(), you'll get the same data, but rather the next 10 bytes in the file. If there are less than 10 bytes left to read in the file, fread() simply reads and returns as many as there are.

The fwrite() Function

You can use the fwrite() function to write data to a file. It requires two arguments: a file handle ($fp) and a string (string of text to write to the file), and writes the contents of string to the file referenced by fp, returning the number of bytes written (or -1 on error). For example, after opening a file with $fp = fopen("data.txt","w"), you could say:


fwrite($fp, "ABCxyz");

This writes the character string "ABCxyz" to the beginning of the file data.txt. Having used write-only mode to open the file, you'll lose any prior contents (and even create the file if it didn't already exist); however, repeating this call appends the same six bytes to what you just written, so that the file contains the characters "ABCxyzABCxyz". Once again, it's the file position indicator in action.


If you specify an integer length as a third argument, it stops writing after length bytes (assuming this is reached before the end of string). For example:


fwrite($fp, "abcdefghij", 4);

writes the first 4 bytes of "abcdefghij" (that is, "abcd") to the file referenced by $fp. If string contained fewer than length bytes, it would be written to the file in full.


Ready to try it out?


Try it Out: A Simple Hit Counter
Start example
One very popular use for Web scripts is the hit counter, which is used to show how many times a Web page has been visited and therefore how popular the Web site is. Hit counters come in different forms, the simplest of which is a text counter. Here's a simple script for such a counter:


<?php
//hit_counter01, php
$counter,_file = "./count.dat";
if(!($fp = fopen($counter_file, "r"))) die ("Cannot open $counter_file.");
$counter = (int) fread($fp, 20);
fclose($fp);

$counter++;

echo "You're visitor No. $counter.";

$fp = fopen ($counter_file, "w");
fwrite ($fp, $counter);
fclose($fp);
?>

Save this script as hit_counter01. php and give it a try.



An error! Not surprising. Note that the script assumes you already have a data file named count.dat in the current directory, that is, the directory from which you ran the script. If this file doesn't exist, an error occurs and the script aborts with an error message to that effect.
You can make the file by saving a blank text file using a text editor (such as Notepad in Windows; don't forget to save as type All Files, or.txt will be appended to the end of the filename), or by using the UNIX touch command if you're running Linux:


> touch count.dat

Try it again.


If you want to, you can specify a count number to start with inside the data file. If you're running the script on the Linux platform, make sure your Web server has read and write permissions to this data file.

End example

How it Works

First of all, the file count.dat (in the current directory) is called by fopen() in read-only mode:


$counter_file = "./count.dat";
if(!($fp = fopen($counter_file, "r"))) die ("Cannot open $counter_file.");

The file handle returned by fopen() is assigned to the variable $fp, which can be used from now on to refer to the open file. The function returns False upon error, so in the event that the specified file doesn't exist or that an error occurs while opening it, the script will die with an appropriate error message.


Next, the script uses the file handle to read the hit count value from the open file. As you can see, the script calls fread() to read 20 bytes from the data file (enough to store at least half a million hits):


$counter = (int) fread($fp, 20);

Because fread() returns a string value, the last number of accesses read from the data file needs to be converted to an integer value, so it's typecast accordingly.


The fclose() function closes the file referenced by the file handle $fp, writing any unwritten data out to the file:


fclose($fp);

After closing the data file, the script increments the counter and tells the visitor how many times the page has been accessed:


$counter++;
echo "You're visitor No. $counter.";

If you stopped here, the counter would never change because the data file remains the same—you have yet to store the incremented counter in the data file. To do that, open the file again by calling fopen(), but this time in write-only mode, a mode that overwrites the existing contents of the file.


$fp = fopen($counter_file, "w");
fwrite($fp, $counter);
fclose($fp);

By calling fwrite() with the $counter variable as the second argument, you ensure that the exact length of the string is written to the data file, so the length argument is not required. Close the file with fclose() when you're finished with it.


You may decide to get creative and make a graphical counter. Used in conjunction with a set of images, the simple text counter script you've produced here can be modified to display graphics. For instance, if you have an image for each counter digit (1.gif,2.gif,3.gif, and so on), you could use each digit value to specify the image associated with the digit:


<?php
//hit_counter02.php
$counter_file = "./count.dat";
$image_dir = "./images";
if(!($fp = fopen($counter_file, "r"))) die ("Cannot open $counter_file.");
$counter = (int) fread($fp, 20);
fclose($fp);

$counter++;

for ($i=0; $i < strlen($counter); $i++) {
   $image_src = $image_dir . "/" . substr($counter, $i, 1) . ".gif";
   $image_tag_str .= "<img src= \"$image_src\" border=\"0\">";
}

echo "You're visitor No. $image_tag_str.";

$fp = fopen($counter_file, "w");
fwrite($fp, $counter);
fclose($fp);
?>

This script (call it hit_counter02.php) simply adds a for loop to step through possible values for the $counter variable, associating each digit value with a corresponding digit image. The loop tests to see if every digit has been processed by testing the length of the string held in the $counter variable. Figure 7-3 shows what it looks like when the script is run.