Приглашаем посетить
Чулков (chulkov.lit-info.ru)

Accessing Files

Previous
Table of Contents
Next

Accessing Files

Working with files on the file systems available to us on the local server is easy in PHP. Occasionally, some confusion arises with regard to permissions and paths, but those issues are addressed in the appropriate sections of this chapter.

Opening Files

You open a file for reading in PHP with the fopen function. This function takes two primary arguments and returns a handle to a resource representing the file, as follows:

$handle = fopen(filename, mode);

The filename is a string representing the path to the file being requested. This can either be a fully qualified path name of the form

'/home/httpd/webapp/logs/errorlog.log'

or it can be a path relative to the current directory, such as the following:

'..\..\Server Documents\Logs\errorlog.log'

The mode parameter specifies exactly how you want to open the file. You use this parameter to tell fopen that you just want to open a file for reading, or create a new file for writing data, among other possibilities. Table 24-1 lists values for this parameter and their meanings. Newly created files have the same user identification and permissions as the executing web server.

Table 24-1. Values for the Mode Parameter of fopen

Mode

Description

r

The file will be opened for reading (only), and the file pointer (see text) will be positioned at the beginning of the file. Writing to the file is not possible.

r+

The file will be opened for both reading and writing, and the file pointer will be positioned at the beginning of the file. Any writes before the end of existing content will overwrite that content.

w

The file will be opened for writing (only). If the file exists, its contents will be deleted, and it will have a size of 0. If the file does not exist, an attempt to create it will be made. The file pointer will be placed at the beginning of the file.

w+

The file will be opened for both reading and writing. If the file exists, its contents will be deleted, and it will have a size of 0. If the file does not exist, an attempt to create it will be made. The file pointer will be placed at the beginning of the file.

a

The file will be opened for appending (writing) only. If the file does not exist, an attempt to create it will be made. The file pointer will be placed at the end of the file.

a+

The file will be opened for reading and appending (writing). If the file does not exist, an attempt to create it will be made. The file pointer will be placed at the end of the file.

x

The file will be created and opened for writing (only). If the file already exists, the function will return FALSE, and a warning will be generated. If the file does not exist, an attempt to create it will be made.

x+

The file will be created and opened for reading and writing. If the file already exists, the function will return FALSE, and a warning will be generated. If the file does not exist, an attempt to create it will be made.


A NOTE ON NEWLINES

Accessing Files

All lines in text files are terminated by a series of line-ending characters. Unfortunately, different operating systems have different line endings, which can be a bit piquing to deal with in PHP code. Windows uses \r\n, Unix uses \n, and Mac OS X systems use \r. Although this is normally not more than a minor nuisance when processing files in code, it can be visually irritating if you then open the given text file in a simple text editor such as notepad.exe or some equivalent.

Windows users have the option of specifying an additional character after the mode flag for the fopen function, which can be either a t or a b. The former tells Windows to operate in a sort of text-translation mode, in which it coverts line-ending characters to the Windows standard of \r\n. The latter, however, tells Windows to leave files alone and not molest them (and treat them just as a sequence of binary data).

In general, in the interest of writing portable web applications that could be used on Windows or Unix servers or elsewhere, you might want to avoid using the t mode flag. Specifying no additional mode flag is the same as using the binary option. For binary data, you definitely want to avoid the t flag because any binary data that happens to contain the character \n might be modified and broken.


To open a log file we have previously written for reading, we might write the following:

  $logFile = @fopen('/home/webapp1/logs/logfile.log', 'r');
  if ($logFile === NULL)
  {
    echo <<<EOM
      <b>We're sorry, but there was an error processing the
      log data. Please try again later.</b>

EOM;
    exit;
  }

  // continue;

It is vital to check errors for each file operation you perform. Far too much code in web applications is written along the following lines:

$file = fopen(...);
$data = get_data_from_file($file)
echo $data;

There is a plethora of reasons why accessing a file might fail, and it is very much in your interest as a web application author to be ready for these. The one downside to using the @ operator to suppress error output, however, is that PHP does not provide a way to find out what the last file error was. This means we are stuck with generic error reporting for many file operationswe merely know that it worked or did not work.

Opening Remote Files

PHP includes a handy feature that significantly increases the power and flexibility of the fopen function by allowing you to specify more than just local filenames for the filename parameter. Indeed, you can specify http://,ftp://, and other protocols via which you might want to access remote files, as follows:

$handle = @fopen('http://fileserver/userdata.csv', 'r');

To use this feature, the allow_url_fopen option in php.ini must be set to "On". This option can only be set in php.ini, and not via the ini_set function.

We do not use this feature throughout this book, and instead make sure that we have set allow_url_fopen to "Off". We do so because the remote access feature is a reasonably large security problem. It is very difficult to trust files coming from another computer, which might or might not have been compromised to include data that could negatively affect our server.

Closing Files

When you have finished with a file, you need to explicitly tell PHP that you have finished by calling the fclose function. This process lets the operating system ensure that all the contents of the file are correctly flushed from any buffers and written to the hard disk eventually:

$file = @fopen(...);
if ($file === NULL)
{
   // error handling
}

// process file data

fclose($file);    // $file is now an invalid handle.

Reading Files

You can use a few different functions to read data from a file provided the mode parameter to the fopen function permits reading. The fgets function reads a line of text from a text file and returns that, whereas the fgetc function reads a single character and returns that. Finally, the fread function reads arbitrary binary data from a file and returns that in a buffer. Attempting to use the fgets function on binary files can produce unpredictable results.

All file handles have what is known as a file pointer, or a cursor that indicates where exactly in the file the next operation will take place. Depending on the exact value of the mode parameter to the fopen function, this will start at either the beginning of the file (0), or at the end of the file (whatever the file size is).

To determine whether you have more data to read, you use the feof function, which returns trUE if the file pointer is at the end of the file, and FALSE otherwise. To read the contents of a text file, you can write code similar to the following:

// returns array of strings (one for each line in the
// input file) on success.  Throws on failure.
function read_text_file($in_filename)
{
  $file = @fopen($in_filename, 'r');
  if ($file === NULL)
    throw new FileOpenException($in_filename);

  $output = array();
  while (!feof($file))
  {
    $buf = fgets($file);
    if ($buf === FALSE)
    {
      fclose($file);
      throw new FileReadException();
    }
    $output[] = $buf;
  }

  fclose($file);
  return $output;
}

We have written this function to throw an exception when there is a failure opening or reading from the file. If you called this function, as follows,

try
{
  $textContents = read_text_file('../logs/logfile.txt');
}
catch (Exception $e)
{
  echo "Unable to read file " . $e.getMessage();
}

you receive in the output array a list of all the lines in the file. Note that this function actually does exist in PHP5. The file function opens a file for reading and returns the contents as an array of strings, although it does not use exceptions to report errors. We demonstrate this here merely as an exercise in working with files.

As the fgets function returns a line from the input text file (it considers a line to be a series of characters terminated by a line-ending character sequence), it leaves those line-ending characters in the string. Therefore, the following text file

a
b

returns the following strings on a Unix system. (On Windows, the \n is replaced by \r\n.)

"\n"
"a\n"
"b\n"
"\n"

PHP provides a function called file_get_contents that does something extremely similar to this. This function loads all the data in a file and places the data in a single output string, rather than an array as the preceding sample function has done.

To read specific-sized chunks of data or to read binary data, you use the fread function. This function takes as parameters the file handle from which to read and the number of bytes of data to read. It returns the data read, or FALSE on failure. If there were less than the specified number of bytes left to read, these are returned and subsequent calls to feof indicate that the file pointer is at the end.

For example, you can use the following to read a binary file 4000 bytes at a time:

$file = @fopen('../binary_file.bin', 'r');
if ($file === NULL)
  throw new FileOpenException('../binary_file.bin');

while (!feof($file))
{
  $buffer = @fread($file, 4000);
  if ($buffer === FALSE)
    throw new FileReadException();

  process_data($buffer);
}

fclose($file);

BINARY STRINGS (BUFFERS) IN PHP

Recall that Chapter 6, "Strings and Characters of the World," mentioned that because we have overridden strlen to always call mb_strlen, we have created a problem for ourselves when it comes to computing the size of a binary buffer.

However, we did mention that we can get around this by passing '8bit' as the character set to the mb_strlen function, which causes it to return the correct value. Therefore, we can write a function as follows:

function binary_size($in_buffer)
{
  return mb_strlen($in_buffer, '8bit');
}

With this, we can safely determine the size of binary data given to us by PHP.


One other useful function that we might want is the filesize function, which tells us the size of the given file. This function can prove helpful for us when deciding whether we want to read it all at once or in smaller pieces:

$size = @filesize('../logs/logfile.log');
if ($size === FALSE)
  throw new FileSizeException('../logs/logfile.log');

if ($size >= 100000)
{
  echo 'Log greater than 100k, showing last 100k only';
  // etc.
}
else
{
  echo 'Showing entire server log';
  // etc.
}

Writing to Files

Writing to files is performed with the fwrite function. The exact way in which you use this function depends on what sort of data you provide it. If you are providing purely textual string data, you can call it with the file handle and data to xwrite to the given file, as follows:

$fruit = array( 'apple', 'orange', 'banana', 'peach');

$file = @fopen('fruity.txt', 'w');
if ($file === NULL)
  throw new FileOpenException('fruity.txt');

foreach ($fruit as $string)
{
  // please note we're using Unix line endings here.
  $result = @fwrite($file, $string . "\n");
  if ($result === FALSE)
    throw new FileWriteException();
}

fclose($file);

For binary data, however, you must specify a third parameter that contains the number of bytes of data to write to the disk. If you do not, what exactly is written out is somewhat unpredictable, and your output file is not likely to contain what you thought it would:

$binary_data = get_image_bytes();

$file = @fopen('imagedata.jpg', 'w');
if ($file === NULL)
  throw new FileOpenException('imagedata.jpg');

$result = @fwrite($file, $binary_data, 
                  mb_strlen($binary_data, '8bit'));
if ($result === FALSE)
  throw new FileWriteException();

fclose($file);

If we were to write a file_copy function, we might choose to do this in chunks, which would require a series of fwrite calls, as follows:

//
// copies in_source to in_dest. Throws on failure.
// 
function file_copy($in_source, $in_dest)
{
  $src = NULL;
  $dest = NULL;

  try
  {
    $src = @fopen($in_source, 'r');
    if ($src === FALSE)
      throw new FileOpenException($in_source);

    $dest = @fopen($in_dest, 'w');
    if ($dest === FALSE)
      throw new FileOpenException($in_dest);

    while (!feof($src))
    {
      $buf = @fread($src, 4000);
      if ($buf === FALSE)
        throw new FileReadException();

      $result = @fwrite($dest, $buf, mb_strlen(buf, '8bit'));
      if ($result === FALSE)
        throw new FileWriteException();
    }  
  }
  catch (Exception $e)
  {
    //
    // clean up and rethrow the exception
    //
    if ($src !== NULL)
      fclose($src);
    if ($dest !== NULL)
    {
      fclose($dest);
      unlink($dest);
    }

    throw $e;
  }

  // clean up and go home!
  fclose($src);
  fclose($dest);
}

Again, because this is a particularly common operation, PHP provides a function called copy that does just this. The preceding code is still useful as an exercise in file manipulation.

When writing to files, you can optionally have PHP flush any currently cached data to disk (well, at least to the operating system's file system codehow and when files are actually written to the hard disk varies widely depending on the file system) by calling the fflush function. This function can slow down your file operations, however, so use it sparingly. Generally, you will use it when you are both reading and writing from a file at the same time and want to be sure that what you have written is flushed to the disk before your next read operation.

File Permissions and Other Information

This section briefly examines a few functions to tell us about the files and directories with which we hope to work, including permissions, existence, access times, and user information.

Permissions

One of the biggest problems you will run into when working with files in a web application environment is that you will have restricted permissions as to what exactly you can read and write. To prevent unwanted errors in your application on calls to fopen and similar functions, you can use a few functions to find out whether you do, indeed, have permissions to read from or write to the given file.

The is_readable function takes a file path and returns a Boolean value indicating whether you have permission to write to the given file:

define('LOG_FILE', 'logfile.log');

if (!is_readable(LOG_FILE))
  throw new FilePermissionsException(LOG_FILE);

$file = @fopen(LOG_FILE, 'r');
if ($file === NULL)
  throw new FileOpenException(LOG_FILE);

// etc.

Similarly, the is_writeable (and its handy cousin is_writable) function lets you know whether a given path is writable. If you specify a directory name, the function tells you whether you have permissions to write to the given directory.

The fileperms function gives you Unix-like file permissions information for a file. This means that on Windows-based systems, you might not be able to use this to get the full range of information actually available. For Unix users, however, the information is the same as that you would use for the chmod command. To print this value as an octal number, you can use the sprintf function (see Chapter 21, "Advanced Output and Output Buffering"), as follows:

echo sprintf("%o", fileperms('logfile.log'));

This typically prints out something such as 100666 for a regular readable file. The trailing 0666 is very familiar to anybody who has used the chmod Unix command, and the 1 at the beginning merely indicates that it is a regular file rather than some special operating system file type, such as symbolic link, device driver file, or named pipe.

File Existence

To see whether a file exists, you can use the file_exists function, which takes a path and returns a Boolean value indicating the existence of the file:

$exists = file_exists('logfile.log');

File Dates and Times

You can also get one of three date/time values for a file, as follows:

  • The last time the file contents were modified, via the filemtime function

  • The last time the file was accessed, even just for reading, via the fileatime function

  • The last time the file was changed in any way, including ownership or permissions, via the filectime function

All three of these functions return a Unix timestamp, which is the number of seconds since January 1, 1970. To convert these into printable dates, you can use the date function.

Owning Users and Groups

Finally, to see the owning user of a path or the owning group of a path on Unix systems, you can use the fileowner and filegroup functions, which return the user ID and group ID of the owning user and group, respectively. To convert these into printable names, you can use the posix_pwuid and posix_getgrgid functions:

// get the information for the current directory
$owner = fileowner('.');
$group = filegroup('.');

// see the documentation for more about these functions.  They
// return arrays of information.
$uinfo = posix_getpwuid($owner);
$ginfo = posix_getgrgid($group);

echo <<<EOM

  User Name of cwd:  <b>{$uinfo['name']}</b> <br/>
  Group Name of cwd: <b>{$ginfo['name']}</b> <br/>
EOM;

Deleting and Renaming Files

To delete files in PHP, you use the unlink function, which removes the file, provided you have permissions to do so. When it is deleted, it is gone forever, so you should be extremely careful with this command. There is also a security risk if you allow users to specify the name of the file for deletion, such as

unlink($_POST['filename']);

because there is nothing stopping them from entering /usr/local/lib/php.ini or C:\windows\php.ini.

To rename a file in PHP, you use the rename function, as follows:

$ret = rename(oldname, newname);

The function returns TRUE on success and FALSE on failure. You must have write permissions in the directory where the file resides.


Previous
Table of Contents
Next