Lesson 10: Using Your File System - TOP10

Lesson 10: Using Your File System

Using simple PHP scripts, you can do anything with your file system—it’s yours, after all! In this chapter, you’ll learn how to do the following:  o Display the contents of a directory. o Create a new file. o Open an existing file and append data to it. o Copy, rename, and delete files.
1. Testing Files Checking for Existence with file_exists() You can test for the existence of a file with the file_exists() function. This requires a string representing an absolute or relative path to a file that may or may not be there. If the file is found, it returns true; otherwise, it returns false.  if ( file_exists("test.txt") ) print "The file exists!";
A File or a Directory? You can confirm that the entity you are testing is a file, as opposed to a directory, with the is_file() function. is_file() requires the file path and returns a Boolean value: if ( is_file( "test.txt" ) ) print "test.txt is a file!";
Conversely, you might want to check that the entity you are 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" ) ) print "/tmp is a directory";  
Checking the Status of a File When you know that a file exists, and it is what you expect it to be, you can then find out some things that you can do with it. Typically, you might want to read, write to, or execute a file. PHP can help you with all of these.
is_readable() tells you whether you can read a file. On UNIX systems, you may be able to see a file but still be barred from reading its contents. is_readable() accepts the file path as a string and returns a Boolean value:  if ( is_readable( "test.txt" ) ) print "test.txt is readable";
is_writable() tells you whether you can write to a file. Once again it requires the file path and returns a Boolean value:  if ( is_writable( "test.txt" ) ) print "test.txt is writable";
is_executable() tells you whether you can run a file, relying on either the file's permissions or its extension depending on your platform. It accepts the file path and returns a Boolean value:  if ( is_executable( "test.txt" )

print "test.txt is executable";
Determining File Size with filesize() Given the path to a file, filesize() attempts to determine and return its size in bytes. It returns false if it encounters problems.  print "The size of test.txt is.. "; print filesize( "test.txt" );
Getting Date Information About a File Sometimes you will 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 when a file was last accessed with fileatime(). 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 these functions in UNIX epoch format. That is, the number of seconds since 1 January 1970. In our examples, we use the date() function to translate this into human readable form. You learn more about date functions in Hour 15, "Working with Dates."  $atime = fileatime( "test.txt" ); print "test.txt was last accessed on "; print date("D d M Y g:i A", $atime); // Sample output: Thu 13 Jan 2000 2:26 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" ); print "test.txt was last modified on "; print date("D d M Y g:i A", $mtime); // Sample output: Thu 13 Jan 2000 2:26 PM]
PHP also allows 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 changes are made to its permissions or ownership. On other platforms, the filectime() returns the creation date.
 $ctime = filectime( "test.txt" ); print "test.txt was last changed on "; print date("D d M Y g:i A", $ctime); // Sample output: Thu 13 Jan 2000 2:26 PM]   Creating a Function That Performs Multiple File Tests   <html>  <head>  <title>Listing 10.8 A function to output the results of multiple file tests</title>  </head>  <body> <?php  $file = "test.txt";  outputFileTestInfo( $file );  function outputFileTestInfo( $f )  { if ( ! file_exists( $f ) )

print "$f does not exist<BR>";  return;  }  print "$f is ".(is_file( $f )?"":"not ")."a file<br>";  print "$f is ".(is_dir( $f )?"":"not ")."a directory<br>";  print "$f is ".(is_readable( $f )?"":"not ")."readable<br>";  print "$f is ".(is_writable( $f )?"":"not ")."writable<br>";  print "$f is ".(is_executable( $f )?"":"not ")."executable<br>";  print "$f is ".(filesize($f))." bytes<br>";  print "$f was accessed on ".date( "D d M Y g:i A", fileatime( $f ) )."<br>";  print "$f was modified on ".date( "D d M Y g:i A", filemtime( $f ) )."<br>";  print "$f was changed on ".date( "D d M Y g:i A", filectime( $f ) )."<br>";  }  ?>  </body> </html> Notice that we have used the ternary operator as a compact way of working with some of these tests. Let's look at one of these in more detail: print "$f is ".(is_file( $f )?"":"not ")."a file<br>";
We use the is_file() function as the right-hand expression of the ternary operator. If this 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 scompact, as follows:  $is_it = is_file( $f )?"":"not "; print "$f is $isit"."a file";   We could, of course, be even clearer with an if statement, but imagine how large the function would become if we had used the following:  if ( is_file( $f ) ) print "$fi is a file<br>"; else print "$fi is not a file<br>";
Because the result of these three approaches is the same, the approach you take becomes broadly a matter of preference.
2. Creating and Deleting Files If a file does not yet exist, you can create one 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, the contents are not disturbed, but the modification date is updated to the time at which the function executed.
You can remove an existing file with the unlink() function. Once again, unlink() accepts a file path:
All functions that create, delete, read, write, or modify files on UNIX systems require that the correct file or directory permissions are set.

3. Opening a File for Writing, Reading, or Appending Before you can work with a file, you must first open it for reading, writing, or both. PHP provides the fopen() function for this. fopen() requires a string containing the file path, followed by a string containing the mode in which the file is to be opened. The most common modes are read ('r'), write ('w'), and append ('a'). fopen() returns an integer you will later use to work with the open file. This integer is known as a file pointer and should be assigned to a variable. To open a file for reading, you would use the following:  $fp = fopen( "test.txt", 'r' );
You would 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 would use this:  $fp = fopen( "test.txt", 'a' ); fopen() returns false if the file cannot be opened for any reason. It is a good idea, therefore, to test the function's return value before proceeding to work with it. You can do this with an if statement:  if ( $fp = fopen( "test.txt", "w" ) ) { // do something with $fp } 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 won't be parsed, and the die() function (which writes a message to the browser and ends the script) will never be reached. Otherwise, the right-hand side of the or operator will be parsed, and the die() function will be called.
Assuming that all is well and you go on to work with your open file, you should remember to close it when you have finished. You can do this by calling fclose(), which requires the file pointer returned from a successful fopen() call as its argument: fclose( $fp ); The mode can be any of the modes listed in the following:

4. Reading from Files PHP provides a number of functions for reading data from files. These enable you to read by the byte, the line, or even the character.
Reading Lines from a File with fgets() and feof() After you have opened a file for reading, you will often need to access it line by line. To read a line from an open file, you can use fgets(), which requires the file pointer returned from fopen() as an argument. You must also pass it an integer as a second argument. This specifies the number of bytes 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.  $line = fgets( $fp, 1024 ); // where $fp is the file pointer returned by fopen()
Although you can read lines with fgets(), you need some way of telling when you have reached the end of the file. The feof() function does this, returning true when the end of the file has been reached and false otherwise. Once again this function requires a file pointer as its argument:  feof( $fp ); // where $fp is the file pointer returned by fopen()
You now have enough information to read a file line by line, as shown in the following:    <html>  <head>  <title>Listing 10.9 Opening and reading a file line by line</title>  </head>  <body>  <?php  $filename = "test.txt";  $fp = fopen( $filename, "r" ) or die("Couldn't open $filename");  while ( ! feof( $fp ) )  {  $line = fgets( $fp, 1024 );  print "$line<br>";  }  ?>  </body>  </html>
We call fopen() with the name of the file that we want to read, using the or operator to ensure that script execution ends if the file cannot be read. This usually occurs if the file does not exist, or (on a UNIX system) if the file's permissions won't allow the script read access to the file. The actual reading takes place in the while statement. 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 the end of the file is reached. Within the code block, we use fgets() to extract a line (or 1024 bytes) of the file. We assign the result to $line and then print it to the browser, 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 pointer as an argument, as well as the number of bytes you want to read. It returns the amount of data you have requested unless the end of the file is reached first.

$chunk = fread( $fp, 16 );
Example: Reading a File with fread()
 <html>  <head>  <title>Listing 10.10 Reading a file with fread()</title>  </head>  <body>  <?php  $filename = "test.txt";  $fp = fopen( $filename, "r" ) or die("Couldn't open $filename");  while ( ! feof( $fp ) )  {  $chunk = fread( $fp, 16 );  print "$chunk<br>";  }  ?>  </body>  </html>
Although fread() allows you to define the amount of data acquired from a file, it won't let you decide the position from which the acquisition begins. You can set this manually with the fseek() function. fseek() enables you to change your current position within a file. It requires a file pointer and an integer representing the offset from the start of the file (in bytes) to which you want to jump:  fseek( $fp, 64 ); Example: Moving Around a File with fseek()
  <html>  <head>  <title>Listing 10.11 Moving around a file with fseek()</title>  </head>  <body>  <?php  $filename = "test.txt";  $fp = fopen( $filename, "r" ) or die("Couldn't open $filename");  $fsize = filesize($filename);  $halfway = (int)( $fsize / 2 );  print "Halfway point: $halfway <BR>\n";  fseek( $fp, $halfway );  $chunk = fread( $fp, ($fsize − $halfway) );  print $chunk;  ?>  </body>  </html>
We calculate the halfway point of our file by dividing the return value of filesize() by 2. We can then use this as the second argument to fseek(), jumping to the halfway point. Finally, we call fread() to extract the second half of the file, printing the result to the browser.
Reading Characters from a File with fgetc()

fgetc() 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 simply need to pass it a file pointer:  $char = fgetc( $fp );
Example: creates a loop that reads the file "test.txt" a character at a time, outputting each character to the browser on its own line.   <html>  <head>  <title>Listing 10.12 Moving around a file with fseek()</title>  </head>  <body>  <?php  $filename = "test.txt";  $fp = fopen( $filename, "r" ) or die("Couldn't open $filename");  while ( ! feof( $fp ) )  {  $char = fgetc( $fp );  print "$char<BR>";  }  ?>  </body>  </html>
5. Writing or Appending to a File The processes for writing to or appending to a file are the same. The difference lies in the fopen() call. When you write to a file, you should use the mode argument "w" when you call fopen():  $fp = fopen( "test.txt", "w" );
All subsequent writing will occur from the start of the file. If the file doesn't already exist, it will be created. If the file already exists, any prior content will be destroyed and replaced by the data you write.
When you append to a file, you should use mode "a" in your fopen() call:  $fp = fopen( "test.txt", "a" ); Any subsequent writes to your file are added to the existing content.
Writing to a File with fwrite() or fputs() fwrite() accepts a file pointer and a string. It then writes the string to the file. fputs() works in exactly the same way.  fwrite( $fp, "hello world" );
fputs( $fp, "hello world" );
Example: uses fwrite() to print to a file. We then append a further string to the same file using fputs().   <html>  <head>  <title>Listing 10.13 Writing and appending to a file</title>  </head>  <body>  <?php  $filename = "test.txt";

 print "Writing to $filename<br>";  $fp = fopen( $filename, "w" ) or die("Couldn't open $filename");  fwrite( $fp, "Hello world\n" );  fclose( $fp );  print "Appending to $filename<br>";  $fp = fopen( $filename, "a" ) or die("Couldn't open $filename");  fputs( $fp, "And another thing\n" );  fclose( $fp );  ?>  </body>  </html>
Locking Files with flock() The techniques you have learned for reading and amending files will work fine if you are only presenting your script to a single user. In the real world, however, you would expect many users to access your projects more or less at 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 will quickly become corrupt.
PHP provides the flock() function to forestall this eventuality. flock() will lock a file to warn other process against writing to or reading from a file while the current process is working with it. flock() requires a valid file pointer, and an integer representing the kind of lock you would like to set. In Table 10.1 we list three kinds of locks you can apply to a file.

Integer Arguments to the flock() Function 

You should call flock() directly after calling fopen() and then call it again to release the lock before closing the file.  $fp = fopen( "test.txt", "a" ); flock( $fp, 2 ); // exclusive lock // write to the file flock( $fp, 1 ); // release the lock 

fclose( $fp );  
Caution Locking with flock() is advisory. Only other scripts that use flock() will respect a lock that you set.  
6. Working with Directories Now that you can test, read, and write to files, turn your attention to directories. PHP provides many functions to work with directories. You will look at how to create, remove, and read them.  
Creating Directories with mkdir() mkdir() enables you to create a directory. mkdir() requires a string representing the path to the directory you want to create and an integer that should be an octal number representing the mode you want to set for the directory. You specify an octal (base 8) number with a leading 0. The mode argument will only have an effect on UNIX systems. The mode should consist of three numbers between 0 and 7, representing permissions for the directory owner, group, and everyone, respectively. This function returns true if it successfully creates a directory, or false if it doesn't. If mkdir() fails, this will usually be because the containing directory has permissions that preclude processes with the script's user ID from writing. mkdir( "testdir", 0777 ); // global read/write/execute permissions  
Removing a Directory with rmdir() rmdir() enables you to remove a directory from the file system, if the process running your script has the right to do so, and if the directory is empty. rmdir() requires only a string representing the path to the directory you want to create. rmdir( "testdir" );  
Opening a Directory for Reading with opendir() Before you can read the contents of a directory, you must first obtain a directory pointer. You can do this with the opendir() function. opendir() requires a string representing the path to the directory you want to open. opendir() returns a directory handle unless the directory is not present or readable, in which case it returns false.  
 $dh = opendir( "testdir" );  
Reading the Contents of a Directory with readdir() Just as you use gets() to read a line from a file, you can use readdir() to read a file or directory name from a directory. readdir() requires a directory handle and returns a string containing the item name. If the end of the directory has been reached, readdir() returns false. Note that readdir() returns only the names of its items, rather than full paths.  
Example: Listing the Contents of a Directory with readdir()  <html> <head> <title>Listing 10.14 Listing the contents of a directory with readdir()</title> </head> <body> <?php $dirname = "testdir"; $dh = opendir( $dirname ); while ( gettype( $file = readdir( $dh )) != boolean ) 

{ if ( is_dir( "$dirname/$file" ) ) print "(D)"; print "$file<br>"; } closedir( $dh ); ?> </body> </html>  
We open our directory for reading with the opendir() function and use a while statement to loop through each of its elements. We call readdir() as part of the while statement's test expression, assigning its result to the $file variable. Within the body of the while statement, we use the $dirname variable in conjunction with the $file variable to create a full file path, which we can then test. If the path leads to a directory, we print "(D)" to the browser. Finally, we print the filename.  
We have used 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 ) ) { print "$file<BR>\n"; }  
The value returned by readdir() will be tested. Because any string other than "0" will resolve to true, there should be no problem. Imagine, however, a directory that contains four files, "0", "1", "2", and "3". The output from the preceding code on my system is as follows: When the loop reaches the file named "0", the string returned by readdir() resolves to false, causing the loop to end. The approach in Listing 10.14 tests the type of the value returned by the readdir() function, thus circumventing the problem.   

Next Post »