SlideShare a Scribd company logo
FROM PHP Programming Solutions.pdf
Testing Files and Directories
 file (or directory) exists on the file system.
 accepts a file path and name as an argument and returns a Boolean value indicating
whether or not that path and name is valid.
Use PHP’s file_exists() function:
<?php
// check to see if file exists
// result: "File exists"
echo file_exists('dummy.txt') ? "File exists" : "File does not exist";
?>
Retrieving File Information
 Gives information about a particular file, such as its size or type.
 The stat()function retrieves file statistics such as the owner and group ID, the file
system block size, the device and inode number, and the file’s creation, access, and
modification times.
 The filesize() function returns the size of the file in bytes; the filetype() function
returns the type of the file (whether file, directory, link, device, or pipe)
 is_readable(), is_writable(), and is_executable() functions return Boolean
values indicating the current status of the file.
<?php
// set the file name
$file = "dummy.txt";
// get file statistics
$info = stat($file);
print_r($info);
// get file type
$type = filetype($file);
echo "File type is $typen";
// get file size
$size = filesize($file);
echo "File size is $size bytesn";
// is file readable?
echo is_readable($file) ? "File is readablen" :
"File is not readablen";
// is file writable?
echo is_writable($file) ? "File is writablen" :
"File is not writablen";
// is file executable?
echo is_executable($file) ? "File is executablen" :
"File is not executablen";
?>
TIP
If PHP’s file functions don’t appear to be working as advertised, try using the absolute file path
to get to the file, instead of a relative path.
NOTE
Some of the information returned by the stat() and filetype() functions, such as inode
numbers and UNIX permission bits, may not be relevant to the Windows version of PHP.
NOTE
The results of a call to stat() are cached. You should use the clearstatcache()function to
reset the cache before your next stat() call to ensure that you always get the most recent file
information.
Reading Files
 read the contents of a local or remote file into a string or an array.
 The file_get_contents() function is a fast and efficient way to read an entire file into
a single string variable, whereupon it can be further processed.
 The file() function is similar, except that it reads a file into an array, with each line of
the file corresponding to an element of the array.
<?php
// set the file name
$file = "dummy.txt";
// read file contents into an array
$dataArr = file($file);
print_r($dataArr);
// read file contents into a string
$dataStr = file_get_contents($file);
echo $dataStr;
?>
 If you’re using an older PHP build that lacks the file_get_contents() function, you
can instead use the fread() function to read a file into a string.
Here’s how:
<?php
// define file to read
$file = "dummy.txt";
// open file
$fp = fopen($file, "rb") or die ("Cannot open file");
// read file contents into string
$dataStr = fread($fp, filesize($file));
echo $dataStr;
// close file
fclose($fp) or die("Cannot close file");
?>
NOTE
In case you were wondering, the options passed to fopen() in the previous listing are used to
open the file in read-only mode ("r") and binary mode ("b").
If you’re trying to read a file over a network link, it may not always be a good idea to slurp up a
file in a single chunk due to network bandwidth considerations. In such situations, the
recommended way to read a file is in ―chunks‖ with the fgets() function, and then combine the
chunks to create a complete string. Here’s an illustration:
<?php
// open file
$fp = fopen("/mnt/net/machine2/hda1/dummy.txt", "rb")
or die ("Cannot open file");
// read contents into a string
while (!feof($fp)) {
$dataStr .= fgets($fp, 1024);
}
// close file
fclose($fp) or die ("Cannot close file");
// display contents
echo $dataStr;
?>
6.4 Reading Line Ranges from a File
Problem
You want to read a particular line or line range from a file.
Solution
Read the file into an array with PHP’s file() function, and then extract the required lines
<?php
// read file into array
$data = file('fortunes.txt') or die("Cannot read file");
// get first line
echo $data[0] . "n";
// get last line
echo end($data) . "n";
// get line 5
echo $data[4] . "n";
// get lines 2-6
$lines = array_slice($data, 1, 5);
echo implode("n", $lines);
?>
Write a custom function that uses the fgets() and fseek() calls to pick one or
more lines out of a file:
<?php
// function to get an arbitrary range of lines
// from a file
function getLines($file, $startLineNum, $endLineNum) {
// check for valid range endpoints
if ($endLineNum < $startLineNum) {
die("Ending line number must be greater than or
equal to starting line number!");
}
// initialize line counter
$lineCounter = 0;
// open the file for reading
$fp = fopen($file, "rb") or die("Cannot open file");
// read contents line by line
while (!feof($fp) && $lineCounter <= $endLineNum) {
// once the starting line number is attained
// save contents to an array
// until the ending line number is attained
$lineCounter++;
$line = fgets($fp);
if ($lineCounter >= $startLineNum && $lineCounter <=
$endLineNum) {
$lineData[] = $line;
}
}
// close the file
fclose($fp) or die ("Cannot close file");
192 P H P P r o g r a m m i n g S o l u t i o n s
// return line range to caller
return $lineData;
}
// return lines 2-6 of file as array
$lines = getLines("fortunes.txt", 2, 6);
print_r($lines);
?>
Comments
Extracting one or more lines from a file is one of the more common problems
developers face, and it’s no surprise that there are so many creative solutions to it.
The first listing outlines the simplest approach, storing the lines of a file in an array
with PHP’s file() function and then using array indexing to extract specific lines
by number.
The second listing offers a more complicated approach, wherein a custom
getLines() function accepts three arguments: a file path, a starting line number,
and an ending line number (for a single line, the latter two will be equal). It then
iterates through the named file, incrementing a counter as each line is processed.
Lines that fall within the supplied range will be saved to an array, which is returned
to the caller once the entire file is processed.
You can also get the first and last lines of a file with a combination of fseek()
and fgets() function calls, as illustrated here:
<?php
// open file
$fp = fopen('fortunes.txt', "rb") or die("Cannot open file");
// get first line
fseek($fp, 0, SEEK_SET);
echo fgets($fp);
// get last line
fseek($fp, 0, SEEK_SET);
while (!feof($fp)) {
$line = fgets($fp);
}
echo $line;
// close file
fclose($fp) or die ("Cannot close file");
?>
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 193
Here, the fseek() function moves the internal file pointer to a specific location
in the file, and the fgets() function retrieves all the data beginning from the
pointer location until the next newline character. To obtain the first line, set the file
pointer to position 0 and call fgets() once; to obtain the last line, keep calling
fgets() until the end of the file is reached and the last return value will represent
the last line.
You should also take a look at the listing in ―6.5: Reading Byte Ranges from
a File‖ for a variant that extracts file contents by bytes instead of lines.
6.5 Reading Byte Ranges from a File
Problem
You want to read a particular byte or byte range from a file.
Solution
Write a custom function encapsulating a combination of fseek(), ftell(), and
fgetc() calls:
<?php
// function to get an arbitrary number of bytes
// from a file
function getBytes($file, $startByte, $endByte) {
// check for valid range endpoints
if ($endByte < $startByte) {
die("Ending byte number must be greater than or
equal to starting byte number!");
}
// open the file for reading
$fp = fopen($file, "rb") or die("Cannot open file");
// seek to starting byte
// retrieve data by character
// until ending byte
fseek ($fp, $startByte, SEEK_SET);
while (!(ftell($fp) > $endByte)) {
$data .= fgetc($fp);
}
194 P H P P r o g r a m m i n g S o l u t i o n s
// close the file
fclose($fp) or die ("Cannot close file");
// return data to caller
return $data;
}
// return first 10 bytes of file
echo getBytes("fortunes.txt", 0, 9);
?>
Comments
The user-defined getBytes() function is similar to the getLines() function
illustrated in the first listing in ―6.4: Reading Line Ranges from a File,‖ with the
primary difference lying in its use of fgetc() instead of fgets().The function
accepts three arguments: a file path, a starting byte number, and an ending byte
number. It then sets the internal file pointer to the starting byte value and loops over
the file character by character, appending the result at each stage to a variable, until
the ending byte value is reached. The variable containing the saved bytes is then
returned to the caller as a string.
6.6 Counting Lines, Words,
and Characters in a File
Problem
You want to count the number of lines, words, and characters in a file.
Solution
Use PHP’s file_get_contents(), strlen(), and str_word_count()
functions to count words and characters in a file:
<?php
// set file name and path
$file = "dummy.txt";
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 195
// read file contents into string
$str = file_get_contents($file) or die ("Cannot read from file");
// read file contents into array
$arr = file ($file) or die ("Cannot read from file");
// count lines
echo "Counted ". sizeof($arr) . " line(s).n";
// count characters, with spaces
$numCharsSpaces = strlen($str);
echo "Counted $numCharsSpaces character(s) with spaces.n";
// count characters, without spaces
$newStr = ereg_replace('[[:space:]]+', '', $str);
$numChars = strlen($newStr);
echo "Counted $numChars character(s) without spaces.n";
// count words
$numWords = str_word_count($str);
echo "Counted $numWords words.n";
?>
Comments
It’s fairly easy to count the number of lines in a file—simply read the file into
an array with file(), which stores each line as an individual array element, and
then count the total number of elements in the array.
Counting words and characters is a little more involved, and requires you to first
read the file into a string with a function such as file_get_contents(). The
number of words and characters (including spaces) can then be obtained by running
the str_word_count() and strlen() functions on the string.
To obtain the number of characters excluding spaces, simple remove all spaces
from the string with ereg_replace() and then obtain the size of the string with
strlen(). You can read more about how this works in the listing in ―1.4: Removing
Whitespace from Strings,‖ and users whose PHP builds don’t support the relativelynewer
str_word_count() function will find an alternative way of counting words
in the listing in ―1.13: Counting Words in a String.‖
196 P H P P r o g r a m m i n g S o l u t i o n s
6.7 Writing Files
Problem
You want to write a string to a file.
Solution
Use the file_put_contents() function:
<?php
// define string to write
$data = "All the world's a stagernAnd all the men and
women merely players";
// write string to file
file_put_contents('shakespeare.txt', $data) or die("Cannot write to
file"); echo "File successfully written.";
?>
Comments
The file_put_contents() function provides an easy way to write data to a file.
The file will be created if it does not already exist, and overwritten if it does. The
return value of the function is the number of bytes written.
TIP
To have file_put_contents() append to an existing file rather than overwrite it
completely, add the optional FILE_APPEND flag to the function call as its third argument.
If you’re using an older PHP build that lacks the file_put_contents()
function, you can use the fwrite() function to write to a file instead. Here’s how:
<?php
// define string to write
$data = "All the world's a stagernAnd all the men and
women merely players";
// open file
$fp = fopen('shakespeare.txt', "wb+") or die ("Cannot open file");
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 197
// lock file
// write string to file
if (flock($fp, LOCK_EX)) {
fwrite($fp, $data) or die("Cannot write to file");
flock($fp, LOCK_UN);
echo "File successfully written.";
} else {
die ("Cannot lock file");
}
// close file
fclose($fp) or die ("Cannot close file");
?>
Here, the fwrite() function is used to write a string to an open file pointer, after
the file has been opened for writing with fopen() and the w+ parameter. Notice
the call to flock() before any data is written to the file—this locks the file, stops
other processes from writing to the file, and thereby reduces the possibility of data
corruption. Once the data has been successfully written, the file is unlocked. Locking
is discussed in greater detail in the listing in ―6.8: Locking and Unlocking Files.‖
TIP
To have fwrite() append to an existing file, rather than overwrite it completely, change the
file mode to ab+ in the fopen() call.
NOTE
The flock() function is not supported on certain file systems, such as the File Allocation
Table (FAT) system and the Network File System (NFS). For an alternative file-locking solution
for
these file systems, look at the listing in “6.8: Locking and Unlocking Files,” and read more about
flock() caveats at http://www.php.net/flock.
6.8 Locking and Unlocking Files
Problem
You want to lock a file before writing to it.
198 P H P P r o g r a m m i n g S o l u t i o n s
Solution
Use the flock() function:
<?php
// open file
$fp = fopen('dummy.txt', "wb+") or die ("Cannot open file");
// lock file
// write string to file
if (flock($fp, LOCK_EX)) {
fwrite($fp, "This is a test.") or die("Cannot write to file");
flock($fp, LOCK_UN);
} else {
die ("Cannot lock file");
}
// close file
fclose($fp) or die ("Cannot close file");
echo "File successfully written.";
?>
Comments
PHP implements both shared and exclusive file locks through its flock() function,
which accepts a file pointer and a flag indicating the lock type (LOCK_EX for
exclusive lock, LOCK_SH for shared lock, and LOCK_UN for unlock). Once a file is
locked with flock(), other processes attempting to write to the file have to wait
until the lock is released; this reduces the possibility of multiple processes trying to
write to the same file simultaneously and corrupting it.
NOTE
PHP’s file locking is advisory, which means that it only works if all processes attempting to
access
the file respect PHP’s locks. This may not always be true in the real world—just because a file is
locked with PHP flock() doesn’t mean that it can’t be modified with an external text editor
like vi—so it’s important to always try and ensure that the processes accessing a file use the
same type of locking and respect each other’s locks.
So long as your file is only written to by a PHP process, flock() will usually suffice; if,
however, your file is accessed by multiple processes, or scripts in different languages, it might
be
worth your time to create a customized locking system that can be understood and used by all
accessing programs. The listing in this section contains some ideas to get you started.
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 199
On certain file systems, the flock() function remains unsupported and will
always return false. Users of older Windows versions are particularly prone to this
problem, as flock() does not work with the FAT file system. If file locking is still
desired on such systems, it becomes necessary to simulate it by means of a userdefined
lock/unlock API. The following listing illustrates this:
<?php
// function to set lock for file
function lock($file) {
return touch("$file.lock");
}
// function to remove lock for file
function unlock($file) {
return unlink ("$file.lock");
}
// function to check if lock exists
function isLocked($file) {
clearstatcache();
return file_exists("$file.lock") ? true : false;
}
// set file name
$file = "dummy.txt";
while ($attemptCount <= 60) {
// if file is not in use
if (!isLocked($file)) {
// lock file
lock($file);
// perform actions
$fp = fopen($file, "ab") or die("Cannot open file");
fwrite($fp, "This is a test.") or die("Cannot write to file");
fclose($fp) or die("Cannot close file");
// unlock file
unlock($file);
echo "File successfully written.";
// break out of loop
break;
} else {
// if file is in use
// increment attempt counter
$attemptCount++;
// sleep for one second
sleep(1);
// try again
}
}
?>
This simple locking API contains three functions: one to lock a file, one to
unlock it, and one to check the status of the lock. A lock is signaled by the presence
of a lock file, which serves as a semaphore; this file is removed when the lock is
released.
The PHP script first checks to see if a lock exists on the file, by looking for the
lock file. If no lock exists, the script obtains a lock and proceeds to make changes
to the file, unlocking it when it’s done. If a lock exists, the script waits one second
and then checks again to see if the previous lock has been released. This check is
performed 60 times, once every second; at the end of it, if the lock has still not been
released, the script gives up and exits.
NOTE
If flock() is not supported on your file system, or if you’re looking for a locking mechanism
that can be used by both PHP and non-PHP scripts, the previous listing provides a basic
framework
to get started. Because the lock is implemented as a file on the system and all programming
languages come with functions to test files, it is fairly easy to port the API to other languages
and
create a locking system that is understood by all processes.
6.9 Removing Lines from a File
Problem
You want to remove a line from a file, given its line number.
Solution
Use PHP’s file() function to read the file into an array, remove the line, and then
write the file back with the file_put_contents() function:
<?php
// set the file name
$file = "fortunes.txt";
// read file into array
$data = file($file) or die("Cannot read file");
// remove third line
unset ($data[2]);
// re-index array
$data = array_values($data);
// write data back to file
file_put_contents($file, implode($data)) or die("Cannot write to file");
echo "File successfully written.";
?>
Comments
The simplest way to erase a line from a file is to read the file into an array, remove
the offending element, and then write the array back to the file, overwriting its
original contents. An important step in this process is the re-indexing of the array
once an element has been removed from it—omit this step and your output file will
display a blank line at the point of surgery.
If your PHP build doesn’t support the file_put_contents() function, you
can accomplish the same result with a combination of fgets() and fwrite().
Here’s how:
<?php
// set the file name
$file = "fortunes.txt";
// set line number to remove
$lineNum = 3;
// open the file for reading
$fp = fopen($file, "rb") or die("Cannot open file");
// read contents line by line
// skip over the line to be removed
while (!feof($fp)) {
$lineCounter++;
$line = fgets($fp);
if ($lineCounter != $lineNum) {
$data .= $line;
}
}
// close the file
fclose($fp) or die ("Cannot close file");
// open the file again for writing
$fp = fopen($file, "rb+") or die("Cannot open file");
// lock file
// write data to it
if (flock($fp, LOCK_EX)) {
fwrite($fp, $data) or die("Cannot write to file");
flock($fp, LOCK_UN);
} else {
die ("Cannot lock file for writing");
}
// close the file
fclose($fp) or die ("Cannot close file");
echo "File successfully written.";
?>
The fgets() function reads the file line by line, appending whatever it finds to
a string. A line counter keeps track of the lines being processed, and takes care of
skipping over the line to be removed so that it never makes it into the data string.
Once the file has been completely processed and its contents have been stored in the
string (with the exception of the line to be removed), the file is closed and reopened
for writing. A lock secures access to the file, and the fwrite() function then writes
the string back to the file, erasing the original contents in the process.
6.10 Processing Directories
Problem
You want to iteratively process all the files in a directory.
Solution
Use PHP’s scandir() function:
<?php
// define directory path
$dir = './test';
// get directory contents as an array
$fileList = scandir($dir) or die ("Not a directory");
// print file names and sizes
foreach ($fileList as $file) {
if (is_file("$dir/$file") && $file != '.' && $file != '..') {
echo "$file: " . filesize("$dir/$file") . "n";
}
}
?>
Comments
PHP’s scandir() function offers a simple solution to this problem—it returns
the contents of a directory as an array, which can then be processed using any loop
construct or array function.
An alternative approach is to use the Iterators available as part of the Standard
PHP Library (SPL). Iterators are ready-made, extensible constructs designed
specifically to loop over item collections, such as arrays and directories. To process
a directory, use a DirectoryIterator, as illustrated here:
<?php
// define directory path
$dir = './test';
// create a DirectoryIterator object
$iterator = new DirectoryIterator($dir);
204 P H P P r o g r a m m i n g S o l u t i o n s
// rewind to beginning of directory
$iterator->rewind();
// iterate over the directory using object methods
// print each file name
while($iterator->valid()) {
if ($iterator->isFile() && !$iterator->isDot()) {
print $iterator->getFilename() . ": " .
$iterator->getSize() . "n";
}
$iterator->next();
}
?>
Here, a DirectoryIterator object is initialized with a directory name, and the
object’s rewind() method is used to reset the internal pointer to the first entry in
the directory. You can then use a while() loop, which runs so long as a valid()
entry exists, to iterate over the directory. Individual file names are retrieved with the
getFilename() method, while you can use the isDot() method to filter out the
entries for the current (.) and parent (..) directories. The next() method moves
the internal pointer forward to the next entry.
You can read more about the DirectoryIterator at http://www.php.net/
~helly/php/ext/spl/.
6.11 Recursively Processing Directories
Problem
You want to process all the files in a directory and its subdirectories.
Solution
Write a recursive function to process the directory and its children:
<?php
// function to recursively process
// a directory and all its subdirectories
function dirTraverse($dir) {
// check if argument is a valid directory
if (!is_dir($dir)) { die("Argument '$dir' is not a directory!"); }
// declare variable to hold file list
global $fileList;
// open directory handle
$dh = opendir($dir) or die ("Cannot open directory '$dir'!");
// iterate over files in directory
while (($file = readdir($dh)) !== false) {
// filter out "." and ".."
if ($file != "." && $file != "..") {
if (is_dir("$dir/$file")) {
// if this is a subdirectory
// recursively process it
dirTraverse("$dir/$file");
} else {
// if this is a file
// do something with it
// for example, reverse file name/path and add to array
$fileList[] = strrev("$dir/$file");
}
}
}
// return the final list to the caller
return $fileList;
}
// recursively process a directory
$result = dirTraverse('./test');
print_r($result);
?>
Comments
As illustrated in the listing in ―6.10: Processing Directories,‖ it’s fairly easy to
process the contents of a single directory with the scandir() function. Dealing
with a series of nested directories is somewhat more complex. The previous listing
illustrates the standard technique, a recursive function that calls itself to travel ever
deeper into the directory tree.
The inner workings of the dirTraverse() function are fairly simple. Every
time the function encounters a directory entry, it checks to see if that value is a file or
a directory. If it’s a directory, the function calls itself and repeats the process until it
reaches the end of the directory tree. If it’s a file, the file is processed—the previous
listing simply reverses the file name and adds it to an array, but you can obviously
replace this with your own custom routine—and then the entire performance is
repeated for the next entry.
Another option is to use the Iterators available as part of the Standard PHP
Library (SPL). Iterators are ready-made, extensible constructs designed specifically
to loop over item collections such as arrays and directories. A predefined Recursive
DirectoryIterator already exists and it’s not difficult to use this for recursive directory
processing. Here’s how:
<?php
// initialize an object
// pass it the directory to be processed
$iterator = new RecursiveIteratorIterator( new
RecursiveDirectoryIterator('./test') );
// iterate over the directory
foreach ($iterator as $key=>$value) {
print strrev($key) . "n";
}
?>
The process of traversing a series of nested directories is significantly simpler
with the SPL at hand. First, initialize a RecursiveDirectoryIterator object and pass it
the path to the top-level directory to be processed. Next, initialize a RecursiveIterat
orIterator object (this is an Iterator designed solely for the purpose of iterating over
other recursive Iterators) and pass it the newly minted RecursiveDirectoryIterator.
You can now process the results with a foreach() loop.
You can read more about the RecursiveDirectoryIterator and the RecursiveIterator
Iterator at http://www.php.net/~helly/php/ext/spl/.
For more examples of recursively processing a directory tree, see the listings in
―6.11: Recursively Processing Directories,‖ ―6.15: Copying Directories,‖ n ―6.17:
Deleting Directories,‖ and ―6.20: Searching for Files in a Directory.‖ You can also read
about recursively processing arrays in the listing in ―4.3: Processing Nested Arrays.‖
6.12 Printing Directory Trees
Problem
You want to print a hierarchical listing of a directory and its contents.
Solution
Write a recursive function to traverse the directory and print its contents:
<pre>
<?php
// function to recursively process
// a directory and all its subdirectories
// and print a hierarchical list
function printTree($dir, $depth=0) {
// check if argument is a valid directory
if (!is_dir($dir)) { die("Argument is not a directory!"); }
// open directory handle
$dh = opendir($dir) or die ("Cannot open directory");
// iterate over files in directory
while (($file = readdir($dh)) !== false) {
// filter out "." and ".."
if ($file != "." && $file != "..") {
if (is_dir("$dir/$file")) {
// if this is a subdirectory (branch)
// print it and go deeper
echo str_repeat(" ", $depth) . " [$file]n";
printTree("$dir/$file", ($depth+1));
} else {
// if this is a file (leaf)
// print it
echo str_repeat(" ", $depth) . " $filen";
}
}
}
}
// recursively process and print directory tree
printTree('./test/');
?>
</pre>
Comments
This listing is actually a variant of the technique outlined in the listing in ―6.11:
Recursively Processing Directories.‖ Here, a recursive function travels through the
named directory and its children, printing the name of every element found. A depth
counter is incremented every time the function enters a subdirectory; the str_
repeat() function uses this depth counter to pad the listing with spaces and thus
simulate a hierarchical tree.
For more examples of recursively processing a directory tree, see the listings in
―6.15: Copying Directories‖ and ―6.17: Deleting Directories.‖
6.13 Copying Files
Problem
You want to copy a file from one location to another.
Solution
Use PHP’s copy() function:
<?php
// set file name
$source = "dummy.txt";
$destination = "dummy.txt.backup";
// copy file if it exists, else exit
if (file_exists($source)) {
copy ($source, $destination) or die ("Cannot copy file '$source'");
echo "File successfully copied.";
} else {
die ("Cannot find file '$source'");
}
?>
Comments
In PHP, creating a copy of a file is as simple as calling the copy() function and
passing it the source and destination file names and paths. The function returns true
if the file is successfully copied.
If what you really want is to create a copy of a directory, visit the listing in ―6.15:
Copying Directories.‖
NOTE
If the destination file already exists, it will be overwritten with no warning by copy(). If this is
not what you want, implement an additional check for the target file with file_exists()
and exit with a warning if the file already exists.
6.14 Copying Remote Files
Problem
You want to create a local copy of a file located on a remote server.
Solution
Use PHP’s file_get_contents() and file_put_contents() functions to
read a remote file and write the retrieved data to a local file:
<?php
// increase script execution time limit
ini_set('max_execution_time', 600);
// set URL of file to be downloaded
$remoteFile = "http://www.some.domain/remote.file.tgz";
// set name of local copy
$localFile = "local.file.tgz";
// read remote file
$data = file_get_contents($remoteFile) or
die("Cannot read from remote file");
// write data to local file
file_put_contents($localFile, $data) or
die("Cannot write to local file");
// display success message
echo "File [$remoteFile] successfully copied to [$localFile]";
?>
210 P H P P r o g r a m m i n g S o l u t i o n s
Comments
Most of PHP’s file functions support reading from remote files. In this listing,
this capability is exploited to its fullest to create a local copy of a remote file.
The file_get_contents() function reads the contents of a remote file into a
string, and the file_put_contents() function then writes this data to a local file,
thereby creating an exact copy. Both functions are binary-safe, so this technique can
be safely used to copy both binary and non-binary files.
6.15 Copying Directories
Problem
You want to copy a directory and all its contents, including subdirectories.
Solution
Write a recursive function to travel through a directory, copying files as it goes:
<?php
// function to recursively copy
// a directory and its subdirectories
function copyRecursive($source, $destination) {
// check if source exists
if (!file_exists($source)) { die("'$source' is not valid"); }
if (!is_dir($destination)) {
mkdir ($destination);
}
// open directory handle
$dh = opendir($source) or die ("Cannot open directory '$source'");
// iterate over files in directory
while (($file = readdir($dh)) !== false) {
// filter out "." and ".."
if ($file != "." && $file != "..") {
if (is_dir("$source/$file")) {
// if this is a subdirectory
// recursively copy it
copyRecursive("$source/$file", "$destination/$file");
} else {
// if this is a file
// copy it
copy ("$source/$file", "$destination/$file")
or die ("Cannot copy file '$file'");
}
}
}
// close directory
closedir($dh);
}
// copy directory recursively
copyRecursive("www/template", "www/site12");
echo "Directories successfully copied.";
?>
Comments
This listing is actually a combination of techniques discussed in the listings in ―6.11:
Recursively Processing Directories‖ and ―6.13: Copying Files.‖ Here, the custom
copyRecursive() function iterates over the source directory and, depending
on whether it finds a file or directory, copies it to the target directory or invokes
itself recursively. The recursion ends when no further subdirectories are left to be
traversed. Note that if the target directory does not exist at any stage, it is created
with the mkdir() function.
6.16 Deleting Files
Problem
You want to delete a file.
Solution
Use PHP’s unlink() function:
<?php
// set file name
$file = "shakespeare.asc";
212 P H P P r o g r a m m i n g S o l u t i o n s
// check if file exists
// if it does, delete it
if (file_exists($file)) {
unlink ($file) or die("Cannot delete file '$file'");
echo "File successfully deleted.";
} else {
die ("Cannot find file '$file'");
}
?>
Comments
To delete a file with PHP, simply call the unlink() function with the file name and
path. The function returns true if the file was successfully deleted.
NOTE
Typically, PHP will not be able to delete files owned by other users; the PHP process can only
delete
files owned by the user it’s running as. This is a common cause of errors, so keep an eye out for
it!
6.17 Deleting Directories
Problem
You want to delete a directory and its contents, including subdirectories.
Solution
Write a recursive function to travel through a directory and its children, deleting files
as it goes:
<?php
// function to recursively delete
// a directory and its subdirectories
function deleteRecursive($dir) {
// check if argument is a valid directory
if (!is_dir($dir)) { die("'$dir' is not a valid directory"); }
// open directory handle
$dh = opendir($dir) or die ("Cannot open directory '$dir'");
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 213
// iterate over files in directory
while (($file = readdir($dh)) !== false) {
// filter out "." and ".."
if ($file != "." && $file != "..") {
if (is_dir("$dir/$file")) {
// if this is a subdirectory
// recursively delete it
deleteRecursive("$dir/$file");
} else {
// if this is a file
// delete it
unlink ("$dir/$file") or die ("Cannot delete file
'$file'");
}
}
}
// close directory
closedir($dh);
// remove top-level directory
rmdir($dir);
}
// delete directory recursively
deleteRecursive("junk/robert/");
echo "Directories successfully deleted.";
?>
Comments
In PHP, the function to remove a directory a rmdir(). Unfortunately, this function
only works if the directory in question is empty. Therefore, to delete a directory, it
is first necessary to iterate over it and delete all the files within it. If the directory
contains subdirectories, those need to be deleted too; you do this by entering them
and erasing their contents.
The most efficient way to accomplish this task is with a recursive function such
as the one in the previous listing, which is a combination of the techniques outlined
in the listing in ―6.11: Recursively Processing Directories‖ and the listing in ―6.16:
Deleting Files.‖ Here, the deleteRecursive() function accepts a directory path
and name and goes to work deleting the files in it. If it encounters a directory, it
invokes itself recursively to enter that directory and clean it up. Once all the contents
of a directory are erased, you use the rmdir() function to remove it completely.
214 P H P P r o g r a m m i n g S o l u t i o n s
6.18 Renaming Files and Directories
Problem
You want to move or rename a file or directory.
Solution
Use PHP’s rename() function:
<?php
// set old and new file/directory names
$oldFile = "home/john";
$newFile = "home/jane";
// check if file/directory exists
// if it does, move/rename it
if (file_exists($oldFile)) {
rename ($oldFile, $newFile)
or die("Cannot move/rename file '$oldFile'");
echo "Files/directories successfully renamed.";
} else {
die ("Cannot find file '$oldFile'");
}
?>
Comments
A corollary to PHP’s copy() function, you can use the rename() function to both
rename and move files. Like copy(), rename()accepts two arguments, a source
file and a destination file, and attempts to rename the former to the latter. It returns
true on success.
6.19 Sorting Files
Problem
You want to sort a file listing.
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 215
Solution
Save the file list to an array, and then use the array_multisort() function to sort
it by one or more attributes:
<?php
// define directory
$dir = "./test/a";
// check if it is a directory
if (!is_dir($dir)) { die("Argument '$dir' is not a directory!"); }
// open directory handle
$dh = opendir($dir) or die ("Cannot open directory '$dir'!");
// iterate over files in directory
while (($file = readdir($dh)) !== false) {
// filter out "." and ".."
if ($file != "." && $file != "..") {
// add an entry to the file list for this file
$fileList[] = array("name" => $file, "size" =>
filesize("$dir/$file"), "date" => filemtime("$dir/$file"));
}
}
// close directory
closedir($dh);
// separate all the elements with the same key
// into individual arrays
foreach ($fileList as $key=>$value) {
$name[$key] = $value['name'];
$size[$key] = $value['size'];
$date[$key] = $value['date'];
}
// now sort by one or more keys
// sort by name
array_multisort($name, $fileList);
print_r($fileList);
216 P H P P r o g r a m m i n g S o l u t i o n s
// sort by date and then size
array_multisort($date, $size, $fileList);
print_r($fileList);
?>
Comments
Here, PHP’s directory functions are used to obtain a list of the files in a directory,
and place them in a two-dimensional array. This array is then processed with PHP’s
array_multisort() function, which is especially good at sorting symmetrical
multidimensional arrays.
The array_multisort() function accepts a series of input arrays and uses
them as sort criteria. Sorting begins with the first array; values in that array that
evaluate as equal are sorted by the next array, and so on. This makes it possible to
sort the file list first by size and then date, or by name and then size, or any other
permutation thereof. Once the file list has been sorted, it can be processed further
or displayed in tabular form.
6.20 Searching for Files in a Directory
Problem
You want to find all the files matching a particular name pattern, starting from a toplevel
search directory.
Solution
Write a recursive function to search the directory and its children for matching file
names:
<?php
// function to recursively search
// directories for matching filenames
function searchRecursive($dir, $pattern) {
// check if argument is a valid directory
if (!is_dir($dir)) { die("Argument '$dir' is not a directory!"); }
// declare array to hold matches
global $matchList;
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 217
// open directory handle
$dh = opendir($dir) or die ("Cannot open directory '$dir'!");
// iterate over files in directory
while (($file = readdir($dh)) !== false) {
// filter out "." and ".."
if ($file != "." && $file != "..") {
if (is_dir("$dir/$file")) {
// if this is a subdirectory
// recursively process it
searchRecursive("$dir/$file", $pattern);
} else {
// if this is a file
// check for a match
// add to $matchList if found
if (preg_match("/$pattern/", $file)) {
$matchList[] = "$dir/$file";
}
}
}
}
// return the final list to the caller
return $matchList;
}
// search for file names containing "ini"
$fileList = searchRecursive("c:/windows", "ini");
print_r($fileList);
?>
Comments
This listing is actually a variant of the technique outlined in the listing in ―6.11:
Recursively Processing Directories.‖ Here, a recursive function travels through the
named directory and its children, using the preg_match() function to check each
file name against the name pattern. Matching file names and their paths are stored in
an array, which is returned to the caller once all subdirectories have been processed.
An alternative approach here involves using the PEAR File_Find class, available
from http://pear.php.net/package/File_Find. This class exposes a
search() method, which accepts a search pattern and a directory path and performs
a recursive search in the named directory for files matching the search pattern. The
return value of the method is an array containing a list of paths to the matching files.
218 P H P P r o g r a m m i n g S o l u t i o n s
Here’s an illustration of this class in action:
<?php
// include File_Find class
include "File/Find.php";
// search recursively for file names containing "tgz"
// returns array of paths to matching files
$fileList = File_Find::search("tgz", "/tmp");
print_r($fileList);
?>
For more examples of recursively processing a directory tree, see the listings in
―6.11: Recursively Processing Directories,‖ ―6.15: Copying Directories,‖ and ―6.17:
Deleting Directories.‖
6.21 Searching for Files in PHP’s
Default Search Path
Problem
You want to check if a particular file exists in PHP’s default search path, and obtain
the full path to it.
Solution
Scan PHP’s include_path for the named file and, if found, obtain the full path to
it with PHP’s realpath() function:
<?php
// function to check for a file
// in the PHP include path
function searchIncludePath($file) {
// get a list of all the directories
// in the include path
$searchList = explode(";", ini_get('include_path'));
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 219
// iterate over the list
// check for the file
// return the path if found
foreach ($searchList as $dir) {
if (file_exists("$dir/$file")) { return realpath("$dir/$file"); }
}
return false;
}
// look for the file "DB.php"
$result = searchIncludePath('DB.php');
echo $result ? "File was found in $result" : "File was not found";
?>
Comments
A special PHP variable defined through the php.ini configuration file, the include_
path variable typically contains a list of directories that PHP will automatically look
in for files include-d or require-d by your script. It is similar to the Windows
$PATH variable, or the Perl @INC variable.
In this listing, the directory list stored in this variable is read into the PHP script
with the ini_get() function, and a foreach() loop is then used to iterate over the
list and check if the file exists. If the file is found, the realpath() function is used
to obtain the full file system path to the file.
6.22 Searching and Replacing
Patterns Within Files
Problem
You want to perform a search/replace operation within one or more files.
Solution
Use PEAR’s File_SearchReplace class:
<?php
// include File_SearchReplace class
include "File/SearchReplace.php";
220 P H P P r o g r a m m i n g S o l u t i o n s
// initialize object
$fsr = new File_SearchReplace('PHP',
'PHP: Hypertext Pre-Processor', array('chapter_01.txt',
'chapter_02.txt'));
// perform the search
// write the changes to the file(s)
$fsr->doReplace();
// get the number of matches
echo $fsr->getNumOccurences() . " match(es) found.";
?>
Comments
To perform search-and-replace operations with one or more files, you’ll need
the PEAR File_SearchReplace class, available from http://pear.php.net/
package/File_SearchReplace. Using this class, it’s easy to replace patterns
inside one or more files.
The object constructor requires three arguments: the search term, the replacement
text, and an array of files to search in. The search/replace operation is performed
with the doReplace() method, which scans each of the named files for the search
term and replaces matches with the replacement text. The total number of matches
can always be obtained with the getNumOccurences() method.
TIP
You can use regular expressions for the search term, and specify an array of directories
(instead of
files) as an optional fourth argument to the object constructor. It’s also possible to control
whether
the search function should comply with Perl or PHP regular expression matching norms. More
information on how to accomplish these tasks can be obtained from the class documentation
and
source code.
6.23 Altering File Extensions
Problem
You want to change all or some of the file extensions in a directory.
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 221
Solution
Use PHP’s glob() and rename() functions:
<?php
// define directory path
$dir = './test';
// define old and new extensions
$newExt = "asc";
$oldExt = "txt";
// search for files matching pattern
foreach (glob("$dir/*.$oldExt") as $file) {
$count++;
// extract the file name (without the extension)
$name = substr($file, 0, strrpos($file, "."));
// rename the file using the name and new extension
rename ($file, "$name.$newExt")
or die ("Cannot rename file '$file'!");
}
echo "$count file(s) renamed.";
?>
Comments
PHP’s glob() function builds a list of files matching a particular pattern, and
returns an array with this information. It’s then a simple matter to iterate over this
array, extract the filename component with substr(), and rename the file with the
new extension.
6.24 Finding Differences Between Files
Problem
You want to perform a UNIX diff on two files.
222 P H P P r o g r a m m i n g S o l u t i o n s
Solution
Use PEAR’s Text_Diff class:
<pre>
<?php
// include Text_Diff class
include "Text/Diff.php";
include "Text/Diff/Renderer.php";
include "Text/Diff/Renderer/unified.php";
// define files to compare
$file1 = "rhyme1.txt";
$file2 = "rhyme2.txt";
// compare files
$diff = &new Text_Diff(file($file1), file($file2));
// initialize renderer and display diff
$renderer = &new Text_Diff_Renderer_unified();
echo $renderer->render($diff);
?>
</pre>
Comments
The UNIX diff program is a wonderful way of quickly identifying differences
between two strings. PEAR’s Text_Diff class, available from http://pear.php
.net/package/Text_Diff, brings this capability to PHP, making it possible to
easily compare two strings and returning the difference in standard diff format.
The input arguments to the Text_Diff object constructor must be two arrays of
string values. Typically, these arrays contain the lines of the files to be compared,
and are obtained with the file() function. The Text_Diff_Renderer class takes care
of displaying the comparison in UNIX diff format, via the render() method of
the object.
As an illustration, here’s some sample output from this listing:
@@ -1,2 +1,3 @@
They all ran after the farmer's wife,
-Who cut off their tales with a carving knife.
+Who cut off their tails with a carving knife,
+Did you ever see such a thing in your life?
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 223
6.25 “Tailing” Files
Problem
You want to ―tail‖ a file, or watch it update in real time, on a Web page.
Solution
Display the output of the UNIX tail program on an auto-refreshing Web page:
<html>
<head>
<meta http-equiv="refresh" content="5;url=<?=$_SERVER['PHP_SELF']?>">
</head>
<body>
<pre>
<?php
// set name of log file
$file = "/tmp/rootproc.log";
// set number of lines to tail
$limit = 10;
// run the UNIX "tail" command and display the output
system("/usr/bin/tail -$limit $file");
?>
</pre>
</body>
</html>
Comments
UNIX administrators commonly use the tail program to watch log files update in
real time. A common requirement in Web applications, especially those that interact
with system processes, is to have this real-time update capability available within the
application itself.
The simplest—though not necessarily most elegant—way to do this is to use
PHP’s system() function to fork an external tail process and display its output on
a Web page. A <meta http-equiv="refresh" ... /> tag at the top of the
page causes it to refresh itself every few seconds, thereby producing an almost
real-time update.
224 P H P P r o g r a m m i n g S o l u t i o n s
NOTE
Forking an external process from PHP is necessarily a resource-intensive process. To avoid
excessive usage of system resources, tune the page refresh interval in this listing to correctly
balance the requirements of real-time monitoring and system resource usage.
6.26 Listing Available Drives
or Mounted File Systems
Problem
You want a list of available drives (Windows) or mounted file systems (UNIX).
Solution
Use the is_dir() function to check which drive letters are valid (Windows):
<?php
// loop from "a" to "z"
// check which are active drives
// place active drives in an array
foreach(range('a','z') as $drive) {
if (is_dir("$drive:")) {
$driveList[] = $drive;
}
}
// print array of active drive letters
print_r($driveList);
?>
Read the /etc/mtab file for a list of active mount points (UNIX):
<?php
// read mount information from mtab file
$lines = file("/etc/mtab") or die ("Cannot read file");
// iterate over lines in file
// get device and mount point
// add to array
foreach ($lines as $line) {
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 225
$arr = explode(" ", $line);
$mountList[$arr[0]] = $arr[1];
}
// print array of active mounts
print_r($mountList);
?>
Comments
For Web applications that interact with the file system—for example, an interactive
file browser or disk quota manager—a common requirement involves obtaining
a list of valid system drives (Windows) or mount points (UNIX). The previous
listings illustrate simple solutions to the problem.
Windows drive letters always consist of a single alphabetic character. So, to find
valid drives, use the is_dir() function to test the range of alphabetic characters,
from A to Z, and retain those for which the function returns true.
A list of active UNIX mounts is usually stored in the system file /etc/mtab
(although your UNIX system may use another file, or even the /proc virtual file
system). So, to find valid drives, simply read this file and parse the information
within it.
6.27 Calculating Disk Usage
Problem
You want to calculate the total disk space used by a disk partition or directory.
Solution
Use PHP’s disk_free_space() and disk_total_space() functions to
calculate the total disk usage for a partition:
<?php
// define partition
// for example, "C:" for Windows
// or "/" for UNIX
$dir = "c:";
// get free space in MB
$free = round(disk_free_space($dir)/1048576);
226 P H P P r o g r a m m i n g S o l u t i o n s
// get total available space in MB
$total = round(disk_total_space($dir)/1048576);
// calculate used space in MB
$used = $total - $free;
echo "$used MB used";
?>
Write a recursive function to calculate the total disk space consumed by a particular
directory:
<?php
// function to recursively process
// a directory and all its subdirectories
function calcDirUsage($dir) {
// check if argument is a valid directory
if (!is_dir($dir)) { die("Argument '$dir' is not a directory!"); }
// declare variable to hold running total
global $byteCount;
// open directory handle
$dh = opendir($dir) or die ("Cannot open directory '$dir'!");
// iterate over files in directory
while (($file = readdir($dh)) !== false) {
// filter out "." and ".."
if ($file != "." && $file != "..") {
if (is_dir("$dir/$file")) {
// if this is a subdirectory
// recursively process it
calcDirUsage("$dir/$file");
} else {
// if this is a file
// add its size to the running total
$byteCount += filesize("$dir/$file");
}
}
}
// return the final list to the caller
return $byteCount;
}
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 227
// calculate disk usage for directory in MB
$bytes = calcDirUsage("c:/windows");
$used = round($bytes/1048576);
echo "$used MB used";
?>
Comments
PHP’s disk_total_space() and disk_free_space() functions return the
maximum and available disk space for a particular drive or partition respectively,
in bytes. Subtracting the latter from the former returns the number of bytes currently
in use on the partition.
Obtaining the disk space used by a specific directory and its subdirectories is
somewhat more complex. The task here involves adding the sizes of all the files
in that directory and its subdirectories to arrive at a total count of bytes used. The
simplest way to accomplish this is with a recursive function such as the one outlined
in the previous listing, where file sizes are calculated and added to a running total.
Directories are deal with recursively, in a manner reminiscent of the technique outlined
in the listing in ―6.11: Recursively Processing Directories.‖ The final sum will be the
total bytes consumed by the directory and all its contents (including subdirectories).
TIP
To convert byte values to megabyte or gigabyte values for display, divide by 1048576 or
1073741824 respectively.
6.28 Creating Temporary Files
Problem
You want to create a temporary file with a unique name, perhaps as a flag or
semaphore for other processes.
Solution
Use PHP’s tempnam() function:
<?php
// create temporary file with prefix "tmp"
$filename = tempnam("/tmp", "tmp");
echo "Temporary file [$filename] successfully created";
?>
228 P H P P r o g r a m m i n g S o l u t i o n s
Comments
PHP’s tempnam() function accepts two arguments, a directory name and a file
prefix, and attempts to create a file using the prefix and a randomly generated
identifier in the specified directory. If the file is successfully created, the function
returns the complete path and name to it—this can then be used by other file
functions to write data to it.
This listing offers an easy way to quickly create a file for temporary use, perhaps
as a signal to other processes. Note, however, that the file created by tempnam()
must be manually deleted with unlink() once it’s no longer required.
TIP
PHP’s tmpfile() function creates a unique, temporary file that exists only for the duration of
the script. Read more about this function at http://www.php.net/tmpfile.
6.29 Finding the System Temporary Directory
Problem
You want to retrieve the path to the system’s temporary directory.
Solution
Use PHP’s tempnam() function to create a temporary file, and then obtain the path
to it:
<?php
// create a temporary file and get its name
// result: "Temporary directory is /tmp"
$tmpfile = tempnam("/this/directory/does/not/exist", "tmp");
unlink ($tmpfile);
echo "Temporary directory is " . dirname($tmpfile);
?>
Comments
The tempnam() function provides an easy way to create a temporary file on the
system. Such a file is typically used as a semaphore or flag for other processes—for
example, it can be used for file locking processes or status indicators. The return
value of the tempnam() function is the full path to the newly minted file. Given this
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 229
file is always created in the system’s temporary directory, running the dirname()
function on the complete file path produces the required information.
NOTE
In this listing, the first parameter to tempnam() is a nonexistent directory path. Why? Well,
tempnam() normally requires you to specify the directory in which the temporary file is to
be created. Passing a nonexistent directory path forces the function to default to the system’s
temporary directory, thereby making it possible to identify the location.
An alternative approach consists of using the PEAR File_Util class, available
at http://pear.php.net/package/File. This class exposes a tmpDir()
method, which returns the path to the system temporary directory. Take a look:
<?php
// include File_Util class
include "File/Util.php";
// get name of system temporary directory
// result: "Temporary directory is /tmp"
$tmpdir = File_Util::tmpDir();
echo "Temporary directory is $tmpdir";
?>
6.30 Converting Between Relative
and Absolute File Paths
Problem
You want to convert a relative path to an absolute path.
Solution
Use PHP’s realpath() function:
<?php
// result: "/usr/local/apache/htdocs" (example)
echo realpath(".");
230 P H P P r o g r a m m i n g S o l u t i o n s
// result: "/usr/local/apache" (example)
echo realpath("..");
// result: "/usr/local/"
echo realpath("/usr/local/mysql/data/../..");
?>
Comments
To convert a relative path to an absolute file path, use PHP’s realpath() function.
This function performs ―path math,‖ translating all the relative locations in a path
string to return a complete absolute file path.
NOTE
On a tangential note, take a look at PEAR’s File_Util class, available from http://pear
.php.net/package/File, which comes with a method to calculate the difference
between two file paths. The following simple example illustrates:
<?php
// include File_Util class
include "File/Util.php";
// define two locations on the filesystem
$begin = "/usr/local/apache";
$end = "/var/spool/mail";
// figure out how to get from one to the other
// result: "../../../var/spool/mail"
echo File_Util::relativePath($end, $begin, "/");
?>
6.31 Parsing File Paths
Problem
You want to extract the path, file name, or extension path from a file path.
C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 231
Solution
Use PHP’s pathinfo() function to automatically split the file path into its
constituent parts:
<?php
// define path and filename
$path = "/etc/sendmail.cf";
// decompose path into constituents
$data = pathinfo($path);
print_r($data);
?>
Comments
The pathinfo() function is one of PHP’s more useful path manipulation functions.
Pass it a file path, and pathinfo() will split it into its individual components. The
resulting associative array contains separate keys for directory name, file name, and
file extension. You can then easily access and use these keys for further processing—
for example, the variable $data['dirname'] will return the value /etc.
TIP
When parsing file paths, also consider using the basename(), dirname(), and
realpath() functions. You can read about these functions in the PHP manual at http://
www.php.net/filesystem.
From PHP Solutions Dynamic Web Design Made Easy, Second Edition.pdf
Chapter 7:Using PHP to Manage Files
PHP has a huge range of functions designed to work with the server�s file system, but finding the right one
for the job isn�t always easy. This chapter cuts through the tangle to show you some practical uses of
these functions, such as reading and writing text files to store small amounts of information without a
database. Loops play an important role in inspecting the contents of the file system, so you�ll also explore
some of the Standard PHP Library (SPL) iterators that are designed to make loops more efficient.
As well as opening local files, PHP can read public files, such as news feeds, on other servers. News
feeds are normally formatted as XML (Extensible Markup Language). In the past, extracting information
from an XML file was tortuous process, but that�s no longer the case thanks to the very aptly named
SimpleXML that was introduced in PHP 5. In this chapter, I�ll show you how to create a drop-down menu
that lists all images in a folder, create a function to select files of a particular type from a folder, pull in a
live news feed from another server, and prompt a visitor to download an image or PDF file rather than open
it in the browser. As an added bonus, you�ll learn how to change the time zone of a date retrieved from
another website.
This chapter covers the following subjects:
Reading and writing files
Listing the contents of a folder
Inspecting files with the SplFileInfo class
Controlling loops with SPL iterators
Using SimpleXML to extract information from an XML file
Consuming an RSS feed
Creating a download link
Checking that PHP has permission to open a file
As I explained in the previous chapter, PHP runs on most Linux servers as nobody or apache. Consequently, a folder
must have minimum access permissions of 755 for scripts to open a file. To create or alter files, you normally need to
set global access permissions of 777, the least secure setting. If PHP is configured to run in your own name, you can
be more restrictive, because your scripts can create and write to files in any folder for which you have read, write, and
execute permissions. On a Windows server, you need write permission to create or update a file. If you need
assistance with changing permissions, consult your hosting company.
Configuration settings that affect file access
Hosting companies can impose further restrictions on file access through php.ini. To find out what
restrictions have been imposed, run phpinfo() on your website, and check the settings in the Core
section. Table 7-1 lists the settings you need to check. Unless you run your own server, you normally
have no control over these settings.
Table 7-1. PHP configuration settings that affect file access
Directive Default value Description
allow_url_fopen On Allows PHP scripts to open public files on the Internet.
allow_url_include Off Controls the ability to include remote files.
open_basedir no value Restricts accessible files to the specified directory tree.
Even if no value is set, restrictions may be set directly in the
server configuration.
safe_mode Off Mainly restricts the ability to use certain functions (for
details, see www.php.net/manual/en/features.safemode.
functions.php). This feature has been deprecated
since PHP 5.3 and will be removed at a future date.
safe_mode_include_dir no value If safe_mode is enabled, user and group ID checks are
skipped when files are included from the specified directory
tree.
Accessing remote files
Arguably the most important setting in Table 7-1 is allow_url_fopen. If it�s disabled, you cannot access
useful external data sources, such as news feeds and public XML documents. Prior to PHP 5.2,
allow_url_fopen also allowed you to include remote files in your pages. This represented a major
security risk, prompting many hosting companies to disabled allow_url_fopen. The security risk was
eliminated in PHP 5.2 by the introduction of a separate setting for including remote files:
allow_url_include, which is disabled by default.
After PHP 5.2 was released, not all hosting companies realized that allow_url_fopen had changed, and
continued to disable it. Hopefully, by the time you read this, the message will have sunk in that
allow_url_fopen allows you to read remote files, but not to include them directly in your scripts. If your
hosting company still disables allow_url_fopen, ask it to turn it on. Otherwise, you won�t be able to use
PHP Solution 7-5. If the hosting company refuses, you should consider moving to one with a better
understanding of PHP.
Configuration settings that affect local file access
If the Local Value column for open_basedir or safe_mode_include_dir displays no value, you can
ignore this section. However, if it does have a value, the meaning depends on whether the value ends with
a trailing slash, like this:
/home/includes/
In this example, you can open or include files only from the includes directory or any of its
subdirectories.
If the value doesn�t have a trailing slash, the value after the last slash acts as a prefix. For example,
/home/inc gives you access to /home/inc, /home/includes, /home/incredible, and so on—
assuming, of course, that they exist or you have the right to create them. PHP Solution 7-1 shows what
happens when you try to access a file outside the limits imposed by open_basedir.
Creating a file storage folder for local testing
Storing data inside your site root is highly insecure, particularly if you need to set global access
permissions on the folder. If you have access to a private folder outside the site root, create your data
store as a subfolder and give it the necessary permissions.
For the purposes of this chapter, I suggest that Windows users create a folder called private on their C
drive. Mac users should create a private folder inside their home folder and then set Read & Write
permissions in the folder�s Info panel as described in the previous chapter.
Reading and writing files
The restrictions described in the previous section reduce the attraction of reading and writing files with
PHP. Using a database is more convenient and offers greater security. However, that assumes you have
access to a database and the necessary knowledge to administer it. So, for small-scale data storage and
retrieval, working directly with text files is worth considering.
Reading files in a single operation
The simplest way to read the contents of a text file is to use file_get_contents() or readfile().
PHP Solution 7-1: Getting the contents of a text file
This PHP solution demonstrates how to use file_get_contents() and readfile(), and explains how
they differ.
1. Create a text file in your private folder, type some text into it, and save it as
filetest_01.txt (or use the version in the ch07 folder).
2. Create a new folder called filesystem in your phpsols site root, and create a PHP file called
get_contents.php in the new folder. Insert the following code inside a PHP block
(get_contents_01.php in the ch07 folder shows the code embedded in a web page, but you
can use just the PHP code for testing purposes):
echo file_get_contents('C:/private/filetest_01.txt');
If you�re on a Mac, amend the pathname like this, using your own Mac username:
echo file_get_contents('/Users/username/private/filetest_01.txt');
If you�re testing on a remote server, amend the pathname accordingly.
For brevity, the remaining examples in this chapter show only the Windows pathname.
3. Save get_contents.php, and view it in a browser. Depending on what you wrote in
filetest_01.txt, you should see something like the following screenshot.
You shouldn�t see any error messages on your local system, unless you typed the code
incorrectly or you didn�t set the correct permissions on a Mac. However, on a remote system,
you may see error messages similar to this:
The error messages in the preceding screenshot were created on a local system to
demonstrate what happens when open_basedir has been set either in php.ini or on the
server. They mean you�re trying to access a file outside your permitted file structure. The first
error message should indicate the allowed paths. On a Windows server, each path is
separated by a semicolon. On Linux, the separator is a colon.
4. Change the code in get_contents.php like this (it�s in get_contents_02.php):
readfile('C:/private/filetest_01.txt');
5. Save get_contents.php, and reload it in your browser. The contents of the text file are
displayed as before.
So, what�s the difference? The original code uses echo to display the contents of the file. The
amended code doesn�t use echo. In other words, file_get_contents() simply gets the
contents of a file, but readfile() also displays it immediately. The advantage of
file_get_contents() is that you can assign the file contents to a variable and process it in
some way before deciding what to do with it.
6. Change the code in get_contents.php like this (or use get_contents_03.php), and load the
page into a browser:
// get the contents of the file
$contents = file_get_contents('C:/private/filetest_01.txt');
// split the contents into an array of words
$words = explode(' ', $contents);
// extract the first four elements of the array
$first = array_slice($words, 0, 4);
// join the first four elements and display
echo implode(' ', $first);
This stores the contents of filetest_01.txt in a variable, which is passed to the explode()
function. This alarmingly named function ―blows apart‖ a string and converts it into an array,
using the first argument to determine where to break the string. In this case, a space is used,
so the contents of the text file are split into an array of words.
The array of words is then passed to the array_slice() function, which takes a slice out of
an array starting from the position specified in the second argument. The third argument
specifies the length of the slice. PHP counts arrays from 0, so this extracts the first four
words.
Finally, implode() does the opposite of explode(), joining the elements of an array and
inserting the first argument between each one. The result is displayed by echo, producing the
following outcome:
Instead of displaying the entire contents of the file, the script now displays only the first four
words. The full string is still stored in $contents.
7. If you need to extract the first few words from a string on a regular basis, you could create a
custom function like this:
function getFirstWords($string, $number) {
$words = explode(' ', $string);
$first = array_slice($words, 0, $number);
return implode(' ', $first);
}
You can extract the first seven words like this (the code is in get_contents_04.php):
$contents = file_get_contents('C:/private/filetest_01.txt');
echo getFirstWords($contents, 7);
8. Among the dangers with accessing external files are that the file might be missing or its name
misspelled. Change the code like this (it�s in get_contents_05.php):
$contents = file_get_contents('C:/private/filetest_01.txt');
if ($contents === false) {
echo 'Sorry, there was a problem reading the file.';
} else {
echo $contents;
}
If the file_get_contents() function can�t open the file, it returns false. Often, you can
test for false by using the logical Not operator like this:
if (!$contents) {
If the file is empty or contains only the number 0, $contents is implicitly false. To make sure
the returned value is explicitly false, you need to use the identical operator (three equal
signs).
9. Test the page in a browser, and it should display the contents of the text file as before.
10. Replace the contents of filetest_01.txt with the number 0. Save the text file, and reload
get_contents.php in the browser. The number displays correctly.
11. Delete the number in the text file, and reload get_contents.php. You should get a blank
screen, but no error message. The file loads, but doesn�t contain anything.
12. Change the code in get_contents.php so that it attempts to load a nonexistent file. When
you load the page, you should see an ugly error message like this:
―Failed to open stream‖ means it couldn�t open the file.
USING PHP TO MANAGE FILES
185
13. This is an ideal place to use the error control operator (see Chapter 4). Insert an @ mark
immediately in front of the call to file_get_contents() like this (the code is in
get_contents_07.php):
$contents = @ file_get_contents('C:/private/filetest0.txt');
14. Test get_contents.php in a browser. You should now see only the following custom error
message:
Always add the error control operator only after testing the rest of a script. When developing, you
need to see error messages to understand why something isn�t working the way you expect.
Text files can be used as a flat-file database—where each record is stored on a separate line, with a tab,
comma, or other delimiter between each field (see http://en.wikipedia.org/
wiki/Flat_file_database). With this sort of file, it�s more convenient to store each line individually in
an array to process with a loop. The file() function builds the array automatically.
PHP Solution 7-2: Reading a text file into an array
To demonstrate the file() function, this PHP solution uses filetest_02.txt, which contains just two
lines as follows:
david, codeslave
chris, bigboss
This will be used as the basis for a simple login system to be developed further in Chapter 9.
1. Create a PHP file called file.php inside the filesystem folder. Insert the following code (or
use file_01.php from the ch07 folder):
<?php
// read the file into an array called $users
$users = file('C:/private/filetest_02.txt');
?>
<pre>
<?php print_r($users); ?>
</pre>
This draws the contents of filetest_02.txt into an array called $users and then passes it
to print_r() to display the contents of the array. The <pre> tags simply make the output
easier to read in a browser.
2. Save the page, and load it in a browser. You should see the following output:
It doesn�t look very exciting, but now that each line is a separate array element, you can loop
through the array to process each line individually.
3. You need to use a counter to keep track of each line; a for loop is the most convenient (see
―The versatile for loop‖ in Chapter 3). To find out how many times the loop should run, pass the
array to the count() function to get its length. Amend the code in file.php like this (or use
file_02.php):
<?php
// read the file into an array called $users
$users = file('C:/private/filetest03.txt');
// loop through the array to process each line
for ($i = 0; $i < count($users); $i++) {
// separate each element and store in a temporary array
$tmp = explode(', ', $users[$i]);
// assign each element of the temporary array to a named array key
$users[$i] = array('name' => $tmp[0], 'password' => $tmp[1]);
}
?>
<pre>
<?php print_r($users); ?>
</pre>
The count() function returns the length of an array, so in this case, the value of
count($users) is 2. This means the first line of the loop is equivalent to this:
for ($i = 0; $i < 2; $i++) {
The loop continues running while $i is less than 2. Since arrays are always counted from 0,
this means the loop runs twice before stopping.
Inside the loop, the current array element ($users[$i]) is passed to the explode() function.
In this case, the separator is defined as a comma followed by a space (', '). However, you
can use any character or sequence of characters: using "t" (see Table 3-4 in Chapter 3) as
the first argument to explode() turns a tab-separated string into an array.
USING PHP TO MANAGE FILES
187
The first line in filetest 02.txt looks like this:
david, codeslave
When this line is passed to explode(), the result is saved in $tmp, so $tmp[0] is david, and
$tmp[1] is codeslave. The final line inside the loop reassigns $tmp[0] to
$users[0]['name'], and $tmp[1] to $users[0]['password'].
The next time the loop runs, $tmp is reused, and $users[1]['name'] becomes chris, and
$users[1]['password'] becomes bigboss.
4. Save file.php, and view it in a browser. The result looks like this:
5. Take a close look at the gap between codeslave and the closing parenthesis of the first
subarray. The file() function doesn�t remove newline characters or carriage returns, so you
need to do it yourself. Pass the final item of $tmp to rtrim() like this:
$users[$i] = array('name' => $tmp[0], 'password' => rtrim($tmp[1]));
The rtrim() function removes whitespace (spaces, tabs, newline characters, and carriage
returns) from the end of a string. It has two companions: ltrim() which removes whitespace
from the beginning of a string, and trim(), which removes whitespace from both ends of a
string.
If you�re working with each line as a whole, pass the entire line to rtrim().
6. As always, you need to check that the file is accessible before attempting to process its
contents, so wrap the main PHP block in a conditional statement like this (see file_03.php):
$textfile = 'C:/private/filetest_02.txt';
if (file_exists($textfile) && is_readable($textfile)) {
// read the file into an array called $users
$users = file($textfile);
// loop through the array to process each line
for ($i = 0; $i < count($users); $i++) {
// separate each element and store in a temporary array
$tmp = explode(', ', $users[$i]);
// assign each element of the temporary array to a named array key
$users[$i] = array('name' => $tmp[0], 'password' => rtrim($tmp[1]));
}
} else {
echo "Can't open $textfile";
}
To avoid typing out the file pathname each time, begin by storing it in a variable.
This simple script extracts a useful array of names and associated passwords. You could also use this
with a series of sports statistics or any data that follows a regular pattern.
Opening and closing files for read/write operations
The functions we have looked at so far do everything in a single pass. However, PHP also has a set of
functions that allow you to open a file, read it and/or write to it, and then close the file. The following are the
most important functions used for this type of operation:
fopen(): Opens a file
fgets(): Reads the contents of a file, normally one line at a time
fread(): Reads a specified amount of a file
fwrite(): Writes to a file
feof(): Determines whether the end of the file has been reached
rewind(): Moves an internal pointer back to the top of the file
fclose(): Closes a file
The first of these, fopen(), is the most difficult to understand, mainly because you need to specify how
the file is to be used once it�s open: fopen() has one read-only mode, three write-only modes, and four
read/write modes. Sometimes, you want to overwrite the existing content. At other times, you may want to
append new material. At yet other times, you may want PHP to create a file if it doesn�t already exist.
The other thing you need to understand is where each mode places the internal pointer when it opens the
file. It�s like the cursor in a word processor: PHP starts reading or writing from wherever the pointer
happens to be when you call fread() or fwrite().
Table 7-2 brings order to the confusion.
USING PHP TO MANAGE FILES
189
Table 7-2. Read/write modes used with fopen()
Type Mode Description
Read-only r Internal pointer initially placed at beginning of file.
Write-only w Existing data deleted before writing. Creates a file if it doesn�t already exist.
a Append mode. New data added at end of file. Creates a file if it doesn�t already
exist.
x Creates a file only if it doesn�t already exist, so no danger of deleting existing
data.
r+ Read/write operations can take place in either order and begin wherever the
internal pointer is at the time. Pointer initially placed at beginning of file. File must
already exist for operation to succeed.
w+ Existing data deleted. Data can be read back after writing. Creates a file if it
doesn�t already exist.
a+ Opens a file ready to add new data at end of file. Also permits data to be read
back after internal pointer has been moved. Creates a file if it doesn�t already
exist.
Read/write
x+ Creates a new file, but fails if a file of the same name already exists. Data can be
read back after writing.
Choose the wrong mode, and you could end up deleting valuable data. You also need to be careful about
the position of the internal pointer. If the pointer is at the end of the file, and you try to read the contents,
you end up with nothing. On the other hand, if the pointer is at the beginning of the file, and you start
writing, you overwrite the equivalent amount of any existing data. ―Moving the internal pointer‖ later in this
chapter explains this in more detail with a practical example.
You work with fopen() by passing it the following two arguments:
The path to the file you want to open
One of the modes listed in Table 7-2
The fopen() function returns a reference to the open file, which can then be used with any of the other
read/write functions. So, this is how you would open a text file for reading:
$file = fopen('C:/private/filetest_02.txt', 'r');
Thereafter, you pass $file as the argument to other functions, such as fgets() and fclose(). Things
should become clearer with a few practical demonstrations. Rather than building the files yourself, you�ll
probably find it easier to use the files in the ch07 folder. I�ll run quickly through each mode.
CHAPTER 7
190
Mac users need to adjust the path to the private folder in the example files to match their setup.
Reading a file with fopen()
The file fopen_read.php contains the following code:
// store the pathname of the file
$filename = 'C:/private/filetest_02.txt';
// open the file in read-only mode
$file = fopen($filename, 'r');
// read the file and store its contents
$contents = fread($file, filesize($filename));
// close the file
fclose($file);
// display the contents
echo nl2br($contents);
If you load this into a browser, you should see the following output:
Unlike file_get_contents(), the function fread() needs to know how much of the file to read. So you
need to supply a second argument indicating the number of bytes. This can be useful if you want, say,
only the first 100 characters of a text file. However, if you want the whole file, you need to pass the file�s
pathname to filesize() to get the correct figure.
The nl2br() function in the final line converts new line characters to HTML <br /> tags.
The other way to read the contents of a file with fopen() is to use the fgets() function, which retrieves
one line at a time. This means that you need to use a while loop in combination with feof() to read right
through to the end of the file. This is done by replacing this line:
$contents = fread($file, filesize($filename));
with this (the full script is in fopen_readloop.php):
// create variable to store the contents
$contents = '';
// loop through each line until end of file
while (!feof($file)) {
// retrieve next line, and add to $contents
$contents .= fgets($file);
}
USING PHP TO MANAGE FILES
191
The while loop uses fgets() to retrieve the contents of the file one line at a time—!feof($file) is the
same as saying until the end of $file—and stores them in $contents.
Both methods are more long-winded than file() or file_get_contents(). However, you need to use
either fread() or fgets() if you want to read and write to a file at the same time.
Replacing content with fopen()
The first of the write-only modes (w) deletes any existing content in a file, so it�s useful for working with
files that need to be updated frequently. You can test the w mode with fopen_write.php, which has the
following PHP code above the DOCTYPE declaration:
<?php
// if the form has been submitted, process the input text
if (isset($_POST['contents'])) {
// open the file in write-only mode
$file = fopen('C:/private/filetest_03.txt', 'w');
// write the contents
fwrite($file, $_POST['contents']);
// close the file
fclose($file);
}
?>
There�s no need to use a loop this time: you�re just writing the value of $contents to the opened file. The
function fwrite() takes two arguments: the reference to the file and whatever you want to write to it.
In other books or scripts on the Internet, you may come across fputs() instead of fwrite(). The
two functions are identical: fputs() is a synonym for fwrite().
If you load fopen_write.php into a browser, type something into the text area, and click Write to file,
PHP creates filetest_03.txt and inserts whatever you typed into the text area. Since this is just a
demonstration, I�ve omitted any checks to make sure that the file was successfully written. Open
filetest_03.txt to verify that your text has been inserted. Now, type something different into the text
area and submit the form again. The original content is deleted from filetest_03.txt and replaced with
the new text. The deleted text is gone forever.
Appending content with fopen()
The append mode is one of the most useful ways of using fopen(), because it adds new content at the
end, preserving any existing content. The main code in fopen_append.php is the same as
fopen_write.php, apart from those elements highlighted here in bold:
// open the file in append mode
$file = fopen('C:/private/filetest_03.txt', 'a');
// write the contents after inserting new line
fwrite($file, PHP_EOL . $_POST['contents']);
// close the file
fclose($file);
CHAPTER 7
192
Notice that I have concatenated PHP_EOL to the beginning of $_POST['contents'] . This is a PHP
constant that represents a new line on any operating system. On Windows, new lines require a carriage
return and newline character, but Macs traditionally use only a carriage return, while Linux uses only a
newline character. PHP_EOL gets round this nightmare by automatically choosing the correct characters
depending on the server�s operating system.
If you load fopen_append.php into a browser and insert some text, it should now be added to the end of
the existing text, as shown in the following screenshot.
This is a very easy way of creating a flat-file database. We�ll come back to append mode in Chapter 9.
Writing a new file with fopen()
Although it can be useful to have a file created automatically with the same name, it may be exactly the
opposite of what you want. To make sure you�re not overwriting an existing file, you can use fopen() with
x mode. The main code in fopen_exclusive.php looks like this (changes are highlighted in bold):
// create a file ready for writing only if it doesn't already exist
$file = fopen('C:/private/filetest_04.txt', 'x');
// write the contents
fwrite($file, $_POST['contents']);
// close the file
fclose($file);
If you load fopen_exclusive.php into a browser, type some text, and click Write to file, the content
should be written to filetest_04.txt in your target folder.
If you try it again, you should get a series of error messages telling you that the file already exists.
Combined read/write operations with fopen()
By adding a plus sign (+) after any of the previous modes, the file is opened for both reading and writing.
You can perform as many read or write operations as you like—and in any order—until the file is closed.
The difference between the combined modes is as follows:
r+: The file must already exist; a new one will not be automatically created. The internal pointer
is placed at the beginning, ready for reading existing content.
w+: Existing content is deleted, so there is nothing to read when the file is first opened.
a+: The file is opened with the internal pointer at the end, ready to append new material, so the
pointer needs to be moved back before anything can be read.
x+: Always creates a new file, so there�s nothing to read when the file is first opened.
Reading is done with fread() or fgets() and writing with fwrite() exactly the same as before. What�s
important is to understand the position of the internal pointer.
USING PHP TO MANAGE FILES
193
Moving the internal pointer
Reading and writing operations always start wherever the internal pointer happens to be, so you normally
want it to be at the beginning of the file for reading, and at the end of the file for writing.
To move the pointer to the beginning of a file, pass the file reference to rewind() like this:
rewind($file);
Moving the pointer to the end of a file is more complex. You need to use fseek(), which moves the pointer
to a location specified by an offset and a PHP constant. The constant that represents the end of the file is
SEEK_END, so an offset of 0 bytes places the pointer at the end. You also need to pass fseek() a
reference to the open file, so all three arguments together look like this:
fseek($file, 0, SEEK_END);
SEEK_END is a constant, so it doesn�t need quotes, and it must be in uppercase. You can also use
fseek() to move the internal pointer to a specific position or relative to its current position. For details,
see http://docs.php.net/manual/en/function.fseek.php.
The file fopen_pointer.php uses the fopen() r+ mode to demonstrate combining several read and
write operations, and the effect of moving the pointer. The main code looks like this:
$filename = 'C:/private/filetest_04.txt';
// open a file for reading and writing
$file = fopen($filename, 'r+');
// the pointer is at the beginning, so existing content is overwritten
fwrite($file, $_POST['contents'] );
// read the contents from the current position
$readRest = '';
while (!feof($file)) {
$readRest .= fgets($file);
}
// reset internal pointer to the beginning
rewind($file);
// read the contents from the beginning (nasty gotcha here)
$readAll = fread($file, filesize($filename));
// pointer now at the end, so write the form contents again
fwrite($file, $_POST['contents']);
// read immediately without moving the pointer
$readAgain = '';
while (!feof($file)) {
$readAgain .= fgets($file);
}
CHAPTER 7
194
// close the file
fclose($file);
The version of this file in the ch07 folder contains code that displays the values of $readRest, $readAll,
and $readAgain to show what happens at each stage of the read/write operations. The existing content in
filetest_04.txt was This works only the first time. When I typed New content. in
fopen_pointer.php and clicked Write to file, I got the results shown here:
Table 7-3 describes the sequence of events.
Table 7-3. Sequence of read/write operations in fopen_pointer.php
Command Position of pointer Result
$file = fopen($filename,'r+'); Beginning of file File opened for processing
fwrite($file, $_POST['contents']); End of write operation Form contents overwrites
beginning of existing content
while (!feof($file)) {
$readRest .= fgets($file);
}
End of file Remainder of existing content
read
rewind($file); Beginning of file Pointer moved back to beginning
of file
$readAll = fread($file, �
filesize($filename));
See text Content read from beginning of file
fwrite($file, $_POST['contents']); At end of previous
operation
Form contents added
at current position of pointer
while (!feof($file)) {
$readAgain .= fgets($file);
}
End of file Nothing read because pointer was
already at end of file
fclose($file); Not applicable File closed and all changes saved
USING PHP TO MANAGE FILES
195
When I opened filetest_04.txt, this is what it contained:
If you study the code in fopen_pointer.php, you�ll notice that the second read operation uses fread().
It works perfectly with this example but contains a nasty surprise. Change the code in
fopen_pointer.php to add the following line after the external file has been opened (it�s commented out
in the download version):
$file = fopen($filename, 'r+');
fseek($file, 0, SEEK_END);
This moves the pointer to the end of the file before the first write operation. Yet, when you run the script,
fread() ignores the text added at the end of the file. This is because the external file is still open, so
filesize() reads its original size. Consequently, you should always use a while loop with !feof() and
fgets() if your read operation takes place after any new content has been written to a file.
The changes to a file with read and write operations are saved only when you call fclose() or when
the script comes to an end. Although PHP saves the file if you forget to use fclose(), you should
always close the file explicitly. Don�t get into bad habits; one day they may cause your code to break
and lose valuable data.
When you create or open a file in a text editor, you can use your mouse to highlight and delete existing
content, or position the insertion point exactly where you want. You don�t have that luxury with a PHP
script, so you need to give it precise instructions. On the other hand, you don�t need to be there when the
script runs. Once you have designed it, it runs automatically every time.
Exploring the file system
PHP�s file system functions can also open directories (folders) and inspect their contents. You put one of
these functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existing
filenames in the images folder and looping through the array to create a unique name for an uploaded file.
From the web developer�s point of view, other practical uses of the file system functions are building dropdown
menus displaying the contents of a folder and creating a script that prompts a user to download a
file, such as an image or PDF document.
Inspecting a folder with scandir()
Let�s take a closer look at the scandir() function, which you used in PHP Solution 6-5. It returns an array
consisting of the files and folders within a specified folder. Just pass the pathname of the folder
(directory) as a string to scandir(), and store the result in a variable like this:
$files = scandir('../images');
CHAPTER 7
196
You can examine the result by using print_r() to display the contents of the array, as shown in the
following screenshot (the code is in scandir.php in the ch07 folder):
As you can see, the array returned by scandir() doesn�t contain only files. The first two items are known
as dot files, which represent the current and parent folders. The third item is a folder called _notes, and
the penultimate item is a folder called thumbs.
The array contains only the names of each item. If you want more information about the contents of a
folder, it�s better to use the DirectoryIterator class.
Inspecting the contents of a folder with DirectoryIterator
The DirectoryIterator class is part of the Standard PHP Library (SPL), which has been part of PHP
since PHP 5.0. The SPL offers a mind-boggling assortment of specialized iterators that allow you to create
sophisticated loops with very little code. As the name suggests, the DirectoryIterator class lets you
loop through the contents of a directory or folder.
Because it�s a class, you instantiate a DirectoryIterator object with the new keyword and pass the
path of the folder you want to inspect to the constructor like this:
$files = new DirectoryIterator('../images');
Unlike scandir(), this doesn�t return an array of filenames—although you can loop through $files in the
same way as an array. Instead, it returns an SplFileInfo object that gives you access to a lot more
information about the folder�s contents than just the filenames. Because it�s an object, you can�t use
print_r() to display its contents.
However, if all you want to do is to display the filenames, you can use a foreach loop like this (the code is
in iterator_01.php in the ch07 folder):
$files = new DirectoryIterator('../images');
foreach ($files as $file) {
echo $file . '<br>';
}
USING PHP TO MANAGE FILES
197
This produces the following result:
Although using echo in the foreach loop displays the filenames, the value stored in $file each time the
loop runs is not a string. In fact, it�s another SplFileInfo object. Table 7-4 lists the main SplFileInfo
methods that can be used to extract useful information about files and folders.
Table 7-4. File information accessible through SplFileInfo methods
Method Returns
getFilename() The name of the file
getPath() The current object�s relative path minus the filename, or minus the folder name if
the current object is a folder
getPathName() The current object�s relative path, including the filename or folder name,
depending on the current type
getRealPath() The current object�s full path, including filename if appropriate
getSize() The size of the file or folder in bytes
isDir() True, if the current object is a folder (directory)
isFile() True, if the current object is a file
isReadable() True, if the current object is readable
isWritable() True, if the current object is writable
CHAPTER 7
198
The RecursiveDirectoryIterator class burrows down into subfolders. To use it, you wrap it in the
curiously named RecursiveIteratorIterator like this (the code is in iterator_03.php):
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('../images'));
foreach ($files as $file) {
echo $file->getRealPath() . '<br>';
}
As the following screenshot shows, the RecursiveDirectoryIterator inspects the contents of all
subfolders, revealing the contents of the thumbs and _notes folders, in a single operation:
However, what if you want to find only certain types of files? Cue another iterator. . . .
Restricting file types with the RegexIterator
The RegexIterator acts as a wrapper to another iterator, filtering its contents using a regular expression
(regex) as a search pattern. To restrict the search to the most commonly used types of image files, you
need to look for any of the following filename extensions: .jpg, .png, or .gif. The regex used to search
for these filename extensions looks like this:
'/.(?:jpg|png|gif)$/i'
In spite of its similarity to Vogon poetry, this regex matches image filename extensions in a caseinsensitive
manner. The code in iterator_04.php has been modified like this:
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('../images'));
$images = new RegexIterator($files, '/.(?:jpg|png|gif)$/i');
foreach ($images as $file) {
echo $file->getRealPath() . '<br>';
}
USING PHP TO MANAGE FILES
199
The original $files object is passed to the RegexIterator constructor, with the regex as the second
argument, and the filtered set is stored in $images. The result is this:
Only image files are now listed. The folders and other miscellaneous files have been removed. Before PHP
5, the same result would have involved many more lines of complex looping.
To learn more about the mysteries of regular expressions (regexes), see my two-part tutorial at
www.adobe.com/devnet/dreamweaver/articles/regular_expressions_pt1.html. As you progress
through this book, you�ll see I make frequent use of regexes. They�re a useful tool to add to your skill
set.
I expect that by this stage, you might be wondering if this can be put to any practical use. OK, let�s build a
drop-down menu of images in a folder.
PHP Solution 7-3: Building a drop-down menu of files
When you work with a database, you often need a list of images or other files in a particular folder. For
instance, you may want to associate a photo with a product detail page. Although you can type the name
of the image into a text field, you need to make sure that the image is there and that you spell its name
correctly. Get PHP to do the hard work by building a drop-down menu automatically. It�s always up-todate,
and there�s no danger of misspelling the name.
1. Create a PHP page called imagelist.php in the filesystem folder. Alternatively, use
imagelist_01.php in the ch07 folder.
CHAPTER 7
200
2. Create a form inside imagelist.php, and insert a <select> element with just one <option>
like this (the code is already in imagelist_01.php):
<form id="form1" name="form1" method="post" action="">
<select name="pix" id="pix">
<option value="">Select an image</option>
</select>
</form>
This <option> is the only static element in the drop-down menu.
3. Amend the form like this:
<form id="form1" name="form1" method="post" action="">
<select name="pix" id="pix">
<option value="">Select an image</option>
<?php
$files = new DirectoryIterator('../images');
$images = new RegexIterator($files, '/.(?:jpg|png|gif)$/i');
foreach ($images as $image) {
?>
<option value="<?php echo $image; ?>"><?php echo $image; ?></option>
<?php } ?>
</select>
</form>
4. Make sure that the path to the images folder is correct for your site�s folder structure.
5. Save imagelist.php, and load it into a browser. You should see a drop-down menu listing all
the images in your images folder, as shown in Figure 7-1.
USING PHP TO MANAGE FILES
201
Figure 7-1. PHP makes light work of creating a drop-down menu of images in a specific folder.
When incorporated into an online form, the filename of the selected image appears in the
$_POST array identified by the name attribute of the <select> element—in this case,
$_POST['pix']. That�s all there is to it!
You can compare your code with imagelist_02.php in the ch07 folder.
PHP Solution 7-4: Creating a generic file selector
The previous PHP solution relies on an understanding of regular expressions. Adapting it to work with
other filename extensions isn�t difficult, but you need to be careful that you don�t accidentally delete a
vital character. Unless regexes or Vogon poetry are your specialty, it�s probably easier to wrap the code in
a function that can be used to inspect a specific folder and create an array of filenames of specific types.
For example, you might want to create an array of PDF document filenames or one that contains both
PDFs and Word documents. Here�s how you do it.
1. Create a new file called buildlist.php in the filesystem folder. The file will contain only
PHP code, so delete any HTML inserted by your editing program.
CHAPTER 7
202
2. Add the following code to the file:
function buildFileList($dir, $extensions) {
if (!is_dir($dir) || !is_readable($dir)) {
return false;
} else {
if (is_array($extensions)) {
$extensions = implode('|', $extensions);
}
}
}
This defines a function called buildFileList(), which takes two arguments:
$dir: The path to the folder from which you want to get the list of filenames.
$extensions: This can be either a string containing a single filename extension
or an array of filename extensions. To keep the code simple, the filename
extensions should not include a leading period.
The function begins by checking whether $dir is a folder and is readable. If it isn�t, the
function returns false, and no more code is executed.
If $dir is OK, the else block is executed. It also begins with a conditional statement that
checks whether $extensions is an array. If it is, it�s passed to implode(), which joins the
array elements with a vertical pipe (|) between each one. A vertical pipe is used in regexes to
indicate alternative values. Let�s say the following array is passed to the function as the
second argument:
array('jpg', 'png', 'gif')
The conditional statement converts it to jpg|png|gif. So, this looks for jpg, or png, or gif.
However, if the argument is a string, it remains untouched.
3. You can now build the regex search pattern and pass both arguments to the
DirectoryIterator and RegexIterator like this:
function buildFileList($dir, $extensions) {
if (!is_dir($dir) || !is_readable($dir)) {
return false;
} else {
if (is_array($extensions)) {
$extensions = implode('|', $extensions);
}
$pattern = "/.(?:{$extensions})$/i";
$folder = new DirectoryIterator($dir);
$files = new RegexIterator($folder, $pattern);
}
}
USING PHP TO MANAGE FILES
203
The regex pattern is built using a string in double quotes and wrapping $extensions in curly
braces to make sure it�s interpreted correctly by the PHP engine. Take care when copying the
code. It�s not exactly easy to read.
4. The final section of the code extracts the filenames to build an array, which is sorted and then
returned. The finished function definition looks like this:
function buildFileList($dir, $extensions) {
if (!is_dir($dir) || !is_readable($dir)) {
return false;
} else {
if (is_array($extensions)) {
$extensions = implode('|', $extensions);
}
// build the regex and get the files
$pattern = "/.(?:{$extensions})$/i";
$folder = new DirectoryIterator($dir);
$files = new RegexIterator($folder, $pattern);
// initialize an array and fill it with the filenames
$filenames = array();
foreach ($files as $file) {
$filenames[] = $file->getFilename();
}
// sort the array and return it
natcasesort($filenames);
return $filenames;
}
}
This initializes an array and uses a foreach loop to assign the filenames to it with the
getFilename() method. Finally, the array is passed to natcasesort(), which sorts it in a
natural, case-insensitive order. What ―natural‖ means is that strings that contain numbers are
sorted in the same way as a person would. For example, a computer normally sorts img12.jpg
before img2.jpg, because the 1 in 12 is lower than 2. Using natcasesort() results in
img2.jpg preceding img12.jpg.
5. To use the function, use as arguments the path to the folder and the filename extensions of
the files you want to find. For example, you could get all Word and PDF documents from a
folder like this:
$docs = buildFileList('folder_name', array('doc', 'docx', 'pdf'));
The code for the buildFileList() function is in buildlist.php in the ch07 folder.
Accessing remote files
Reading, writing, and inspecting files on your local computer or on your own website is useful. But
allow_url_fopen also gives you access to publicly available documents anywhere on the Internet. You
can�t directly include files from other servers—not unless allow_url_include is on—but you can read
CHAPTER 7
204
the content, save it to a variable, and manipulate it with PHP functions before incorporating it in your own
pages or saving the information to a database. You can also write to documents on a remote server as
long as the owner sets the appropriate permissions.
A word of caution is in order here. When extracting material from remote sources for inclusion in your own
pages, there�s a potential security risk. For example, a remote page might contain malicious scripts
embedded in <script> tags or hyperlinks. Unless the remote page supplies data in a known format from a
trusted source—such as product details from the Amazon.com database, weather information from a
government meteorological office, or a newsfeed from a newspaper or broadcaster—sanitize the content
by passing it to htmlentities() (see PHP Solution 5-3). As well as converting double quotes to &quot;,
htmlentities() converts < to &lt; and > to &gt;. This displays tags in plain text, rather than treating
them as HTML.
If you want to permit some HTML tags, use the strip_tags() function instead. If you pass a string to
strip_tags(), it returns the string with all HTML tags and comments stripped out. It also removes PHP
tags. A second, optional argument is a list of tags that you want preserved. For example, the following
strips out all tags except paragraphs and first- and second-level headings:
$stripped = strip_tags($original, '<p><h1><h2>');
For an in-depth discussion of security issues, see Pro PHP Security by Chris Snyder and Michael
Southwell (Apress, 2005, ISBN: 978-1-59059-508-4).
Consuming news and other RSS feeds
Some of the most useful remote sources of information that you might want to incorporate in your sites
come from RSS feeds. RSS stands for Really Simple Syndication, and it�s a dialect of XML. XML is similar
to HTML in that it uses tags to mark up content. Instead of defining paragraphs, headings, and images,
XML tags are used to organize data in a predictable hierarchy. XML is written in plain text, so it�s
frequently used to share information between computers that might be running on different operating
systems.
Figure 7-2 shows the typical structure of an RSS 2.0 feed. The whole document is wrapped in a pair of
<rss> tags. This is the root element, similar to the <html> tags of a web page. The rest of the document is
wrapped in a pair of <channel> tags, which always contains the following three elements that describe the
RSS feed: <title>, <description>, and <link>.
USING PHP TO MANAGE FILES
205
Figure 7-2. The main contents of an RSS feed are in the item elements.
In addition to the three required elements, the <channel> can contain many other elements, but the
interesting material is to be found in the <item> elements. In the case of a news feed, this is where the
individual news items can be found. If you�re looking at the RSS feed from a blog, the <item> elements
normally contain summaries of the blog posts.
Each <item> element can contain several elements, but those shown in Figure 7-2 are the most
common—and usually the most interesting:
<title>: The title of the item
<link>: The URL of the item
<pubDate>: Date of publication
<description>: Summary of the item
This predictable format makes it easy to extract the information from an RSS feed using SimpleXML.
You can find the full RSS Specification at www.rssboard.org/rss-specification. Unlike most
technical specifications, it�s written in plain language, and easy to read.
Using SimpleXML
As long as you know the structure of an XML document, SimpleXML does what it says on the tin: it makes
extracting information from XML simple. The first step is to pass the URL of the XML document to
simplexml_load_file(). You can also load a local XML file by passing the path as an argument. For
example, this gets the world news feed from the BBC:
$feed = simplexml_load_file('http://feeds.bbci.co.uk/news/world/rss.xml');
This creates an instance of the SimpleXMLElement class. All the elements in the feed can now be
accessed as properties of the $feed object, using the names of the elements. With an RSS feed, the
<item> elements can be accessed as $feed->channel->item.
CHAPTER 7
206
To display the <title> of each <item>, create a foreach loop like this:
foreach ($feed->channel->item as $item) {
echo $item->title . '<br>';
}
If you compare this with Figure 7-2, you can see that you access elements by chaining the element names
with the -> operator until you reach the target. Since there are multiple <item> elements, you need to use
a loop to tunnel further down. Alternatively, use array notation like this:
$feed->channel->item[2]->title
This gets the <title> of the third <item> element. Unless you want only a specific value, it�s simpler to
use a loop.
With that background out of the way, let�s use SimpleXML to display the contents of a news feed.
PHP Solution 7-5: Consuming an RSS news feed
This PHP solution shows how to extract the information from a live news feed using SimpleXML and
display it in a web page. It also shows how to format the <pubDate> element to a more user-friendly format
and how to limit the number of items displayed using the LimitIterator class.
1. Create a new page called newsfeed.php in the filesystem folder. This page will contain a
mixture of PHP and HTML.
2. The news feed chosen for this PHP solution is the BBC World News. A condition of using most
news feeds is that you acknowledge the source. So add The Latest from BBC News
formatted as an <h1> heading at the top of the page.
See http://news.bbc.co.uk/1/hi/help/rss/4498287.stm for the full terms and conditions of
using a BBC news feed on your own site.
3. Create a PHP block below the heading, and add the following code to load the feed:
$url = 'http://feeds.bbci.co.uk/news/world/rss.xml';
$feed = simplexml_load_file($url);
4. Use a foreach loop to access the <item> elements and display the <title> of each one:
foreach ($feed->channel->item as $item) {
echo $item->title . '<br>';
}
5. Save newsfeed.php, and load the page in a browser. You should see a long list of news items
similar to Figure 7-3.
USING PHP TO MANAGE FILES
207
Figure 7-3. The news feed contains a large number of items.
6. The normal feed often contains 50 or more items. That�s fine for a news site, but you probably
want a shorter selection in your own site. Use another SPL iterator to select a specific range of
items. Amend the code like this:
$url = 'http://feeds.bbci.co.uk/news/world/rss.xml';
$feed = simplexml_load_file($url, 'SimpleXMLIterator');
$filtered = new LimitIterator($feed->channel->item, 0 , 4);
foreach ($filtered as $item) {
echo $item->title . '<br>';
}
To use SimpleXML with an SPL iterator, you need to supply the name of the
SimpleXMLIterator class as the second argument to simplexml_load_file(). You can
then pass the SimpleXML element you want to affect to an iterator constructor.
In this case, $feed->channel->item is passed to the LimitIterator constructor. The
LimitIterator takes three arguments: the object you want to limit, the starting point
(counting from 0), and the number of times you want the loop to run. This code starts at the
first item and limits the number of items to four.
The foreach loop now loops over the $filtered result. If you test the page again, you�ll see
just four titles, as shown in Figure 7-4. Don�t be surprised if the selection of headlines is
different from before. The BBC News website is updated every minute.
CHAPTER 7
208
Figure 7-4. The LimitIterator restricts the number of items displayed.
7. Now that you have limited the number of items, amend the foreach loop to wrap the <title>
elements in a link to the original article, and display the <pubDate> and <description> items.
The loop looks like this:
foreach ($filtered as $item) { ?>
<h2><a href="<?php echo $item->link; ?>"><?php echo $item->title; �
?></a></h2>
<p class="datetime"><?php echo $item->pubDate; ?></p>
<p><?php echo $item->description; ?></p>
<?php } ?>
8. Save the page, and test it again. The links take you directly to the relevant news story on the
BBC website. The news feed is now functional, but the <pubDate> format follows the format
laid down in the RSS specification, as shown in the next screenshot:
9. To format the date and time in a more user-friendly way, pass $item->pubDate to the
DateTime class constructor, and then use the DateTime format() method to display it.
10. Change the code in the foreach loop like this:
<p class="datetime"><?php $date= new DateTime($item->pubDate);
echo $date->format('M j, Y, g:ia'); ?></p>
This reformats the date like this:
The mysterious PHP formatting strings for dates are explained in Chapter 14.
USING PHP TO MANAGE FILES
209
11. That looks a lot better, but the time is still expressed in GMT (London time). If most of your
site�s visitors live on the East Coast of the United States, you probably want to show the local
time. That�s no problem with a DateTime object. Use the setTimezone() method to change to
New York time. You can even automate the display of EDT (Eastern Daylight Time) or EST
(Eastern Standard Time) depending on whether daylight saving time is in operation. Amend the
code like this:
<p class="datetime"><?php $date = new DateTime($item->pubDate);
$date->setTimezone(new DateTimeZone('America/New_York'));
$offset = $date->getOffset();
$timezone = ($offset == -14400) ? ' EDT' : ' EST';
echo $date->format('M j, Y, g:ia') . $timezone; ?></p>
To create a DateTimeZone object, you pass it as an argument one of the time zones listed at
http://docs.php.net/manual/en/timezones.php. This is the only place that the
DateTimeZone object is needed, so it has been created directly as the argument to the
setTimezone() method.
There isn�t a dedicated method that tells you whether daylight saving time is in operation, but
the getOffset() method returns the number of seconds the time is offset from Coordinated
Universal Time (UTC). The following line determines whether to display EDT or EST:
$timezone = ($offset == -14400) ? ' EDT' : ' EST';
This uses the value of $offset with the conditional operator. In summer, New York is 4 hours
behind UTC (–14440 seconds). So, if $offset is –14400, the condition equates to true, and
EDT is assigned to $timezone. Otherwise, EST is used.
Finally, the value of $timezone is concatenated to the formatted time. The string used for
$timezone has a leading space to separate the time zone from the time. When the page is
loaded, the time is adjusted to the East Coast of the United States like this:
12. All the page needs now is smartening up with CSS. Figure 7-5 shows the finished news feed
styled with newsfeed.css in the styles folder.
You can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friends
of ED, 2008, ISBN: 978-1-4302-1011-5).
CHAPTER 7
210
Figure 7-5. The live news feed requires only a dozen lines of PHP code.
Although I have used the BBC News feed for this PHP solution, it should work with any RSS 2.0 feed. For
example, you can try it locally with http://rss.cnn.com/rss/edition.rss. Using a CNN news feed in a
public website requires permission from CNN. Always check with the copyright holder for terms and
conditions before incorporating a feed into a website.
Creating a download link
A question that crops up regularly in online forums is, ―How do I create a link to an image (or PDF file) that
prompts the user to download it?‖ The quick solution is to convert the file into a compressed format, such
as ZIP. This frequently results in a smaller download, but the downside is that inexperienced users may
not know how to unzip the file, or they may be using an older operating system that doesn�t include an
extraction facility. With PHP file system functions, it�s easy to create a link that automatically prompts the
user to download a file in its original format.
PHP Solution 7-6: Prompting a user to download an image
The script in this PHP solution sends the necessary HTTP headers, opens the file, and outputs its
contents as a binary stream.
1. Create a PHP file called download.php in the filesystem folder. The full listing is given in the
next step. You can also find it in download.php in the ch07 folder.
USING PHP TO MANAGE FILES
211
2. Remove any default code created by your script editor, and insert the following code:
<?php
// define error page
$error = 'http://localhost/phpsols/error.php';
// define the path to the download folder
$filepath = 'C:/xampp/htdocs/phpsols/images/';
$getfile = NULL;
// block any attempt to explore the filesystem
if (isset($_GET['file']) && basename($_GET['file']) == $_GET['file']) {
$getfile = $_GET['file'];
} else {
header("Location: $error");
exit;
}
if ($getfile) {
$path = $filepath . $getfile;
// check that it exists and is readable
if (file_exists($path) && is_readable($path)) {
// get the file's size and send the appropriate headers
$size = filesize($path);
header('Content-Type: application/octet-stream');
header('Content-Length: '. $size);
header('Content-Disposition: attachment; filename=' . $getfile);
header('Content-Transfer-Encoding: binary');
// open the file in read-only mode
// suppress error messages if the file can't be opened
$file = @fopen($path, 'r');
if ($file) {
// stream the file and exit the script when complete
fpassthru($file);
exit;
} else {
header("Location: $error");
}
} else {
header("Location: $error");
}
The only two lines that you need to change in this script are highlighted in bold type. The first
defines $error, a variable that contains the URL of your error page. The second line that
needs to be changed defines the path to the folder where the download file is stored.
The script works by taking the name of the file to be downloaded from a query string appended
to the URL and saving it as $getfile. Because query strings can be easily tampered with,
CHAPTER 7
212
$getfile is initially set to NULL. This is an important security measure. If you fail to do this,
you could give a malicious user access to any file on your server.
The opening conditional statement uses basename() to make sure that an attacker cannot
request a file, such as one that stores passwords, from another part of your file structure. As
explained in Chapter 4, basename() extracts the filename component of a path, so if
basename($_GET['file']) is different from $_GET['file'], you know there�s an attempt to
probe your server, and you can stop the script from going any further by using the header()
function to redirect the user to the error page.
After checking that the requested file exists and is readable, the script gets the file�s size,
sends the appropriate HTTP headers, and opens the file in read-only mode using fopen().
Finally, fpassthru() dumps the file to the output buffer. But if the file can�t be opened or
doesn�t exist, the user is redirected to the error page.
3. Test the script by creating another page and add a couple of links to download.php. Add a
query string at the end of each link with file= followed by the name a file to be downloaded.
You�ll find a page called getdownloads.php in the ch07 folder, which contains the following
two links:
<p><a href="download.php?file=maiko.jpg">Download image 1</a></p>
<p><a href="download.php?file=basin.jpg">Download image 2</a></p>
4. Click one of the links, and the browser should present you with a dialog box prompting you to
download the file or choose a program to open it, as shown in Figure 7-6.
Figure 7-6. The browser prompts the user to download the image, rather than opening it directly.
USING PHP TO MANAGE FILES
213
5. Select Save File, and click OK, and the file should be saved rather than displayed. Click
Cancel to abandon the download. Whichever button you click, the original page remains in the
browser window. The only time download.php should load into the browser is if the file cannot
be opened. That�s why it�s important to send the user to an error page if there�s a problem.
I�ve demonstrated download.php with image files, but it can be used for any type of file because the
headers send the file as a binary stream.
This script relies on header() to send the appropriate HTTP headers to the browser. It is vital to
ensure that there are no new lines or whitespace ahead of the opening PHP tag. If you have removed
all whitespace and still get an error message saying “headers already sent,” your editor may have
inserted invisible control characters at the beginning of the file. Some editing programs insert the byte
order mark (BOM), which is known to cause problems with the ability to use the header() function.
Check your program preferences to make sure the option to insert the BOM is deselected.
Chapter review
The file system functions aren�t particularly difficult to use, but there are many subtleties that can turn a
seemingly simple task into a complicated one. It�s important to check that you have the right permissions.
Even when handling files in your own website, PHP needs permission to access any folder where you want
to read files or write to them.
The SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine the
contents of folders. Used in combination with the SplFileInfo methods and the RegexIterator, you
can quickly find files of a specific type within a folder or folder hierarchy.
When dealing with remote data sources, you need to check that allow_url_fopen hasn�t been disabled.
One of the most common uses of remote data sources is extracting information from RSS news feeds or
XML documents, a task that takes only a few lines of code thanks to SimpleXML.
In the next two chapters, we�ll put some of the PHP solutions from this chapter to further practical use
when working with images and building a simple user authentication system.
CHAPTER 7
214
__

More Related Content

PPT
File handling in c
PPTX
C Programming Unit-5
PPT
File handling in c
PPTX
File Handling and Command Line Arguments in C
PPTX
UNIT 10. Files and file handling in C
PPTX
File handling in c
PPT
PPT
Files and Directories in PHP
File handling in c
C Programming Unit-5
File handling in c
File Handling and Command Line Arguments in C
UNIT 10. Files and file handling in C
File handling in c
Files and Directories in PHP

What's hot (18)

PPT
file
PPTX
File Management in C
PPTX
File in C language
PPT
File handling
PPT
File in c
PDF
Files in C
PPTX
Files in php
PPTX
File handling in C
PPT
Files in c++ ppt
PDF
PHP file handling
PPTX
Pf cs102 programming-8 [file handling] (1)
PPT
File in cpp 2016
DOCX
Php files
PPTX
Filesin c++
PPT
File handling in 'C'
PDF
Python - File operations & Data parsing
PPT
File handling in C++
file
File Management in C
File in C language
File handling
File in c
Files in C
Files in php
File handling in C
Files in c++ ppt
PHP file handling
Pf cs102 programming-8 [file handling] (1)
File in cpp 2016
Php files
Filesin c++
File handling in 'C'
Python - File operations & Data parsing
File handling in C++
Ad

Viewers also liked (6)

PPT
PHP - Introduction to File Handling with PHP
PPTX
Php File Operations
PPTX
Uploading a file with php
PDF
PHP Files: An Introduction
PDF
Rails 5 subjective overview
PPTX
PHP - Introduction to File Handling with PHP
Php File Operations
Uploading a file with php
PHP Files: An Introduction
Rails 5 subjective overview
Ad

Similar to Files nts (20)

PPTX
File Organization
PDF
FILES IN C
PPTX
FS_module_functions.pptx
PPTX
Module 5_Reading and Writing Files.pptx.
PPTX
INput output stream in ccP Full Detail.pptx
PPTX
file handling in python using exception statement
PDF
Advance C Programming UNIT 4-FILE HANDLING IN C.pdf
PPTX
File Handlingb in java. A brief presentation on file handling
PPTX
Programming C- File Handling , File Operation
PPT
file handling1
PPS
C programming session 08
DOCX
PPTX
PPS-II UNIT-5 PPT.pptx
PPT
Unit5
PPT
file_handling_in_c.ppt
PPT
7 Data File Handling
PPTX
File Handling
PDF
Data file handling
PPTX
object oriented programming in PHP & Functions
PPT
data file handling
File Organization
FILES IN C
FS_module_functions.pptx
Module 5_Reading and Writing Files.pptx.
INput output stream in ccP Full Detail.pptx
file handling in python using exception statement
Advance C Programming UNIT 4-FILE HANDLING IN C.pdf
File Handlingb in java. A brief presentation on file handling
Programming C- File Handling , File Operation
file handling1
C programming session 08
PPS-II UNIT-5 PPT.pptx
Unit5
file_handling_in_c.ppt
7 Data File Handling
File Handling
Data file handling
object oriented programming in PHP & Functions
data file handling

Recently uploaded (20)

PPTX
SOPHOS-XG Firewall Administrator PPT.pptx
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PDF
Mushroom cultivation and it's methods.pdf
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPT
Teaching material agriculture food technology
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
Machine learning based COVID-19 study performance prediction
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Empathic Computing: Creating Shared Understanding
PPTX
1. Introduction to Computer Programming.pptx
PPTX
Programs and apps: productivity, graphics, security and other tools
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Heart disease approach using modified random forest and particle swarm optimi...
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Approach and Philosophy of On baking technology
PDF
Accuracy of neural networks in brain wave diagnosis of schizophrenia
PPTX
Spectroscopy.pptx food analysis technology
SOPHOS-XG Firewall Administrator PPT.pptx
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
Mushroom cultivation and it's methods.pdf
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Building Integrated photovoltaic BIPV_UPV.pdf
Teaching material agriculture food technology
Advanced methodologies resolving dimensionality complications for autism neur...
Machine learning based COVID-19 study performance prediction
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Empathic Computing: Creating Shared Understanding
1. Introduction to Computer Programming.pptx
Programs and apps: productivity, graphics, security and other tools
Encapsulation_ Review paper, used for researhc scholars
Heart disease approach using modified random forest and particle swarm optimi...
Unlocking AI with Model Context Protocol (MCP)
Approach and Philosophy of On baking technology
Accuracy of neural networks in brain wave diagnosis of schizophrenia
Spectroscopy.pptx food analysis technology

Files nts

  • 1. FROM PHP Programming Solutions.pdf Testing Files and Directories  file (or directory) exists on the file system.  accepts a file path and name as an argument and returns a Boolean value indicating whether or not that path and name is valid. Use PHP’s file_exists() function: <?php // check to see if file exists // result: "File exists" echo file_exists('dummy.txt') ? "File exists" : "File does not exist"; ?> Retrieving File Information  Gives information about a particular file, such as its size or type.  The stat()function retrieves file statistics such as the owner and group ID, the file system block size, the device and inode number, and the file’s creation, access, and modification times.  The filesize() function returns the size of the file in bytes; the filetype() function returns the type of the file (whether file, directory, link, device, or pipe)  is_readable(), is_writable(), and is_executable() functions return Boolean values indicating the current status of the file. <?php // set the file name $file = "dummy.txt"; // get file statistics $info = stat($file); print_r($info); // get file type $type = filetype($file); echo "File type is $typen"; // get file size $size = filesize($file); echo "File size is $size bytesn"; // is file readable? echo is_readable($file) ? "File is readablen" : "File is not readablen"; // is file writable? echo is_writable($file) ? "File is writablen" : "File is not writablen"; // is file executable? echo is_executable($file) ? "File is executablen" : "File is not executablen"; ?> TIP If PHP’s file functions don’t appear to be working as advertised, try using the absolute file path to get to the file, instead of a relative path. NOTE Some of the information returned by the stat() and filetype() functions, such as inode numbers and UNIX permission bits, may not be relevant to the Windows version of PHP. NOTE
  • 2. The results of a call to stat() are cached. You should use the clearstatcache()function to reset the cache before your next stat() call to ensure that you always get the most recent file information. Reading Files  read the contents of a local or remote file into a string or an array.  The file_get_contents() function is a fast and efficient way to read an entire file into a single string variable, whereupon it can be further processed.  The file() function is similar, except that it reads a file into an array, with each line of the file corresponding to an element of the array. <?php // set the file name $file = "dummy.txt"; // read file contents into an array $dataArr = file($file); print_r($dataArr); // read file contents into a string $dataStr = file_get_contents($file); echo $dataStr; ?>  If you’re using an older PHP build that lacks the file_get_contents() function, you can instead use the fread() function to read a file into a string. Here’s how: <?php // define file to read $file = "dummy.txt"; // open file $fp = fopen($file, "rb") or die ("Cannot open file"); // read file contents into string $dataStr = fread($fp, filesize($file)); echo $dataStr; // close file fclose($fp) or die("Cannot close file"); ?> NOTE In case you were wondering, the options passed to fopen() in the previous listing are used to open the file in read-only mode ("r") and binary mode ("b"). If you’re trying to read a file over a network link, it may not always be a good idea to slurp up a file in a single chunk due to network bandwidth considerations. In such situations, the recommended way to read a file is in ―chunks‖ with the fgets() function, and then combine the chunks to create a complete string. Here’s an illustration: <?php // open file $fp = fopen("/mnt/net/machine2/hda1/dummy.txt", "rb") or die ("Cannot open file"); // read contents into a string while (!feof($fp)) { $dataStr .= fgets($fp, 1024); } // close file fclose($fp) or die ("Cannot close file"); // display contents echo $dataStr; ?>
  • 3. 6.4 Reading Line Ranges from a File Problem You want to read a particular line or line range from a file. Solution Read the file into an array with PHP’s file() function, and then extract the required lines <?php // read file into array $data = file('fortunes.txt') or die("Cannot read file"); // get first line echo $data[0] . "n"; // get last line echo end($data) . "n"; // get line 5 echo $data[4] . "n"; // get lines 2-6 $lines = array_slice($data, 1, 5); echo implode("n", $lines); ?> Write a custom function that uses the fgets() and fseek() calls to pick one or more lines out of a file: <?php // function to get an arbitrary range of lines // from a file function getLines($file, $startLineNum, $endLineNum) { // check for valid range endpoints if ($endLineNum < $startLineNum) { die("Ending line number must be greater than or equal to starting line number!"); } // initialize line counter $lineCounter = 0; // open the file for reading $fp = fopen($file, "rb") or die("Cannot open file"); // read contents line by line while (!feof($fp) && $lineCounter <= $endLineNum) { // once the starting line number is attained // save contents to an array // until the ending line number is attained $lineCounter++; $line = fgets($fp); if ($lineCounter >= $startLineNum && $lineCounter <= $endLineNum) { $lineData[] = $line; } } // close the file fclose($fp) or die ("Cannot close file"); 192 P H P P r o g r a m m i n g S o l u t i o n s // return line range to caller return $lineData; } // return lines 2-6 of file as array $lines = getLines("fortunes.txt", 2, 6); print_r($lines); ?> Comments Extracting one or more lines from a file is one of the more common problems
  • 4. developers face, and it’s no surprise that there are so many creative solutions to it. The first listing outlines the simplest approach, storing the lines of a file in an array with PHP’s file() function and then using array indexing to extract specific lines by number. The second listing offers a more complicated approach, wherein a custom getLines() function accepts three arguments: a file path, a starting line number, and an ending line number (for a single line, the latter two will be equal). It then iterates through the named file, incrementing a counter as each line is processed. Lines that fall within the supplied range will be saved to an array, which is returned to the caller once the entire file is processed. You can also get the first and last lines of a file with a combination of fseek() and fgets() function calls, as illustrated here: <?php // open file $fp = fopen('fortunes.txt', "rb") or die("Cannot open file"); // get first line fseek($fp, 0, SEEK_SET); echo fgets($fp); // get last line fseek($fp, 0, SEEK_SET); while (!feof($fp)) { $line = fgets($fp); } echo $line; // close file fclose($fp) or die ("Cannot close file"); ?> C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 193 Here, the fseek() function moves the internal file pointer to a specific location in the file, and the fgets() function retrieves all the data beginning from the pointer location until the next newline character. To obtain the first line, set the file pointer to position 0 and call fgets() once; to obtain the last line, keep calling fgets() until the end of the file is reached and the last return value will represent the last line. You should also take a look at the listing in ―6.5: Reading Byte Ranges from a File‖ for a variant that extracts file contents by bytes instead of lines. 6.5 Reading Byte Ranges from a File Problem You want to read a particular byte or byte range from a file. Solution Write a custom function encapsulating a combination of fseek(), ftell(), and fgetc() calls: <?php // function to get an arbitrary number of bytes // from a file function getBytes($file, $startByte, $endByte) { // check for valid range endpoints if ($endByte < $startByte) { die("Ending byte number must be greater than or equal to starting byte number!"); } // open the file for reading $fp = fopen($file, "rb") or die("Cannot open file");
  • 5. // seek to starting byte // retrieve data by character // until ending byte fseek ($fp, $startByte, SEEK_SET); while (!(ftell($fp) > $endByte)) { $data .= fgetc($fp); } 194 P H P P r o g r a m m i n g S o l u t i o n s // close the file fclose($fp) or die ("Cannot close file"); // return data to caller return $data; } // return first 10 bytes of file echo getBytes("fortunes.txt", 0, 9); ?> Comments The user-defined getBytes() function is similar to the getLines() function illustrated in the first listing in ―6.4: Reading Line Ranges from a File,‖ with the primary difference lying in its use of fgetc() instead of fgets().The function accepts three arguments: a file path, a starting byte number, and an ending byte number. It then sets the internal file pointer to the starting byte value and loops over the file character by character, appending the result at each stage to a variable, until the ending byte value is reached. The variable containing the saved bytes is then returned to the caller as a string. 6.6 Counting Lines, Words, and Characters in a File Problem You want to count the number of lines, words, and characters in a file. Solution Use PHP’s file_get_contents(), strlen(), and str_word_count() functions to count words and characters in a file: <?php // set file name and path $file = "dummy.txt"; C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 195 // read file contents into string $str = file_get_contents($file) or die ("Cannot read from file"); // read file contents into array $arr = file ($file) or die ("Cannot read from file"); // count lines echo "Counted ". sizeof($arr) . " line(s).n"; // count characters, with spaces $numCharsSpaces = strlen($str); echo "Counted $numCharsSpaces character(s) with spaces.n"; // count characters, without spaces $newStr = ereg_replace('[[:space:]]+', '', $str); $numChars = strlen($newStr); echo "Counted $numChars character(s) without spaces.n"; // count words $numWords = str_word_count($str); echo "Counted $numWords words.n"; ?>
  • 6. Comments It’s fairly easy to count the number of lines in a file—simply read the file into an array with file(), which stores each line as an individual array element, and then count the total number of elements in the array. Counting words and characters is a little more involved, and requires you to first read the file into a string with a function such as file_get_contents(). The number of words and characters (including spaces) can then be obtained by running the str_word_count() and strlen() functions on the string. To obtain the number of characters excluding spaces, simple remove all spaces from the string with ereg_replace() and then obtain the size of the string with strlen(). You can read more about how this works in the listing in ―1.4: Removing Whitespace from Strings,‖ and users whose PHP builds don’t support the relativelynewer str_word_count() function will find an alternative way of counting words in the listing in ―1.13: Counting Words in a String.‖ 196 P H P P r o g r a m m i n g S o l u t i o n s 6.7 Writing Files Problem You want to write a string to a file. Solution Use the file_put_contents() function: <?php // define string to write $data = "All the world's a stagernAnd all the men and women merely players"; // write string to file file_put_contents('shakespeare.txt', $data) or die("Cannot write to file"); echo "File successfully written."; ?> Comments The file_put_contents() function provides an easy way to write data to a file. The file will be created if it does not already exist, and overwritten if it does. The return value of the function is the number of bytes written. TIP To have file_put_contents() append to an existing file rather than overwrite it completely, add the optional FILE_APPEND flag to the function call as its third argument. If you’re using an older PHP build that lacks the file_put_contents() function, you can use the fwrite() function to write to a file instead. Here’s how: <?php // define string to write $data = "All the world's a stagernAnd all the men and women merely players"; // open file $fp = fopen('shakespeare.txt', "wb+") or die ("Cannot open file"); C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 197 // lock file // write string to file if (flock($fp, LOCK_EX)) { fwrite($fp, $data) or die("Cannot write to file"); flock($fp, LOCK_UN); echo "File successfully written.";
  • 7. } else { die ("Cannot lock file"); } // close file fclose($fp) or die ("Cannot close file"); ?> Here, the fwrite() function is used to write a string to an open file pointer, after the file has been opened for writing with fopen() and the w+ parameter. Notice the call to flock() before any data is written to the file—this locks the file, stops other processes from writing to the file, and thereby reduces the possibility of data corruption. Once the data has been successfully written, the file is unlocked. Locking is discussed in greater detail in the listing in ―6.8: Locking and Unlocking Files.‖ TIP To have fwrite() append to an existing file, rather than overwrite it completely, change the file mode to ab+ in the fopen() call. NOTE The flock() function is not supported on certain file systems, such as the File Allocation Table (FAT) system and the Network File System (NFS). For an alternative file-locking solution for these file systems, look at the listing in “6.8: Locking and Unlocking Files,” and read more about flock() caveats at http://www.php.net/flock. 6.8 Locking and Unlocking Files Problem You want to lock a file before writing to it. 198 P H P P r o g r a m m i n g S o l u t i o n s Solution Use the flock() function: <?php // open file $fp = fopen('dummy.txt', "wb+") or die ("Cannot open file"); // lock file // write string to file if (flock($fp, LOCK_EX)) { fwrite($fp, "This is a test.") or die("Cannot write to file"); flock($fp, LOCK_UN); } else { die ("Cannot lock file"); } // close file fclose($fp) or die ("Cannot close file"); echo "File successfully written."; ?> Comments PHP implements both shared and exclusive file locks through its flock() function, which accepts a file pointer and a flag indicating the lock type (LOCK_EX for exclusive lock, LOCK_SH for shared lock, and LOCK_UN for unlock). Once a file is locked with flock(), other processes attempting to write to the file have to wait until the lock is released; this reduces the possibility of multiple processes trying to write to the same file simultaneously and corrupting it. NOTE
  • 8. PHP’s file locking is advisory, which means that it only works if all processes attempting to access the file respect PHP’s locks. This may not always be true in the real world—just because a file is locked with PHP flock() doesn’t mean that it can’t be modified with an external text editor like vi—so it’s important to always try and ensure that the processes accessing a file use the same type of locking and respect each other’s locks. So long as your file is only written to by a PHP process, flock() will usually suffice; if, however, your file is accessed by multiple processes, or scripts in different languages, it might be worth your time to create a customized locking system that can be understood and used by all accessing programs. The listing in this section contains some ideas to get you started. C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 199 On certain file systems, the flock() function remains unsupported and will always return false. Users of older Windows versions are particularly prone to this problem, as flock() does not work with the FAT file system. If file locking is still desired on such systems, it becomes necessary to simulate it by means of a userdefined lock/unlock API. The following listing illustrates this: <?php // function to set lock for file function lock($file) { return touch("$file.lock"); } // function to remove lock for file function unlock($file) { return unlink ("$file.lock"); } // function to check if lock exists function isLocked($file) { clearstatcache(); return file_exists("$file.lock") ? true : false; } // set file name $file = "dummy.txt"; while ($attemptCount <= 60) { // if file is not in use if (!isLocked($file)) { // lock file lock($file); // perform actions $fp = fopen($file, "ab") or die("Cannot open file"); fwrite($fp, "This is a test.") or die("Cannot write to file"); fclose($fp) or die("Cannot close file"); // unlock file unlock($file); echo "File successfully written."; // break out of loop break; } else { // if file is in use // increment attempt counter $attemptCount++; // sleep for one second sleep(1); // try again } } ?> This simple locking API contains three functions: one to lock a file, one to
  • 9. unlock it, and one to check the status of the lock. A lock is signaled by the presence of a lock file, which serves as a semaphore; this file is removed when the lock is released. The PHP script first checks to see if a lock exists on the file, by looking for the lock file. If no lock exists, the script obtains a lock and proceeds to make changes to the file, unlocking it when it’s done. If a lock exists, the script waits one second and then checks again to see if the previous lock has been released. This check is performed 60 times, once every second; at the end of it, if the lock has still not been released, the script gives up and exits. NOTE If flock() is not supported on your file system, or if you’re looking for a locking mechanism that can be used by both PHP and non-PHP scripts, the previous listing provides a basic framework to get started. Because the lock is implemented as a file on the system and all programming languages come with functions to test files, it is fairly easy to port the API to other languages and create a locking system that is understood by all processes. 6.9 Removing Lines from a File Problem You want to remove a line from a file, given its line number. Solution Use PHP’s file() function to read the file into an array, remove the line, and then write the file back with the file_put_contents() function: <?php // set the file name $file = "fortunes.txt"; // read file into array $data = file($file) or die("Cannot read file"); // remove third line unset ($data[2]); // re-index array $data = array_values($data); // write data back to file file_put_contents($file, implode($data)) or die("Cannot write to file"); echo "File successfully written."; ?> Comments The simplest way to erase a line from a file is to read the file into an array, remove the offending element, and then write the array back to the file, overwriting its original contents. An important step in this process is the re-indexing of the array once an element has been removed from it—omit this step and your output file will display a blank line at the point of surgery. If your PHP build doesn’t support the file_put_contents() function, you can accomplish the same result with a combination of fgets() and fwrite(). Here’s how: <?php // set the file name $file = "fortunes.txt"; // set line number to remove $lineNum = 3; // open the file for reading $fp = fopen($file, "rb") or die("Cannot open file");
  • 10. // read contents line by line // skip over the line to be removed while (!feof($fp)) { $lineCounter++; $line = fgets($fp); if ($lineCounter != $lineNum) { $data .= $line; } } // close the file fclose($fp) or die ("Cannot close file"); // open the file again for writing $fp = fopen($file, "rb+") or die("Cannot open file"); // lock file // write data to it if (flock($fp, LOCK_EX)) { fwrite($fp, $data) or die("Cannot write to file"); flock($fp, LOCK_UN); } else { die ("Cannot lock file for writing"); } // close the file fclose($fp) or die ("Cannot close file"); echo "File successfully written."; ?> The fgets() function reads the file line by line, appending whatever it finds to a string. A line counter keeps track of the lines being processed, and takes care of skipping over the line to be removed so that it never makes it into the data string. Once the file has been completely processed and its contents have been stored in the string (with the exception of the line to be removed), the file is closed and reopened for writing. A lock secures access to the file, and the fwrite() function then writes the string back to the file, erasing the original contents in the process. 6.10 Processing Directories Problem You want to iteratively process all the files in a directory. Solution Use PHP’s scandir() function: <?php // define directory path $dir = './test'; // get directory contents as an array $fileList = scandir($dir) or die ("Not a directory"); // print file names and sizes foreach ($fileList as $file) { if (is_file("$dir/$file") && $file != '.' && $file != '..') { echo "$file: " . filesize("$dir/$file") . "n"; } } ?> Comments PHP’s scandir() function offers a simple solution to this problem—it returns the contents of a directory as an array, which can then be processed using any loop construct or array function. An alternative approach is to use the Iterators available as part of the Standard PHP Library (SPL). Iterators are ready-made, extensible constructs designed
  • 11. specifically to loop over item collections, such as arrays and directories. To process a directory, use a DirectoryIterator, as illustrated here: <?php // define directory path $dir = './test'; // create a DirectoryIterator object $iterator = new DirectoryIterator($dir); 204 P H P P r o g r a m m i n g S o l u t i o n s // rewind to beginning of directory $iterator->rewind(); // iterate over the directory using object methods // print each file name while($iterator->valid()) { if ($iterator->isFile() && !$iterator->isDot()) { print $iterator->getFilename() . ": " . $iterator->getSize() . "n"; } $iterator->next(); } ?> Here, a DirectoryIterator object is initialized with a directory name, and the object’s rewind() method is used to reset the internal pointer to the first entry in the directory. You can then use a while() loop, which runs so long as a valid() entry exists, to iterate over the directory. Individual file names are retrieved with the getFilename() method, while you can use the isDot() method to filter out the entries for the current (.) and parent (..) directories. The next() method moves the internal pointer forward to the next entry. You can read more about the DirectoryIterator at http://www.php.net/ ~helly/php/ext/spl/. 6.11 Recursively Processing Directories Problem You want to process all the files in a directory and its subdirectories. Solution Write a recursive function to process the directory and its children: <?php // function to recursively process // a directory and all its subdirectories function dirTraverse($dir) { // check if argument is a valid directory if (!is_dir($dir)) { die("Argument '$dir' is not a directory!"); } // declare variable to hold file list global $fileList; // open directory handle $dh = opendir($dir) or die ("Cannot open directory '$dir'!"); // iterate over files in directory while (($file = readdir($dh)) !== false) { // filter out "." and ".." if ($file != "." && $file != "..") { if (is_dir("$dir/$file")) { // if this is a subdirectory // recursively process it dirTraverse("$dir/$file"); } else { // if this is a file // do something with it // for example, reverse file name/path and add to array
  • 12. $fileList[] = strrev("$dir/$file"); } } } // return the final list to the caller return $fileList; } // recursively process a directory $result = dirTraverse('./test'); print_r($result); ?> Comments As illustrated in the listing in ―6.10: Processing Directories,‖ it’s fairly easy to process the contents of a single directory with the scandir() function. Dealing with a series of nested directories is somewhat more complex. The previous listing illustrates the standard technique, a recursive function that calls itself to travel ever deeper into the directory tree. The inner workings of the dirTraverse() function are fairly simple. Every time the function encounters a directory entry, it checks to see if that value is a file or a directory. If it’s a directory, the function calls itself and repeats the process until it reaches the end of the directory tree. If it’s a file, the file is processed—the previous listing simply reverses the file name and adds it to an array, but you can obviously replace this with your own custom routine—and then the entire performance is repeated for the next entry. Another option is to use the Iterators available as part of the Standard PHP Library (SPL). Iterators are ready-made, extensible constructs designed specifically to loop over item collections such as arrays and directories. A predefined Recursive DirectoryIterator already exists and it’s not difficult to use this for recursive directory processing. Here’s how: <?php // initialize an object // pass it the directory to be processed $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator('./test') ); // iterate over the directory foreach ($iterator as $key=>$value) { print strrev($key) . "n"; } ?> The process of traversing a series of nested directories is significantly simpler with the SPL at hand. First, initialize a RecursiveDirectoryIterator object and pass it the path to the top-level directory to be processed. Next, initialize a RecursiveIterat orIterator object (this is an Iterator designed solely for the purpose of iterating over other recursive Iterators) and pass it the newly minted RecursiveDirectoryIterator. You can now process the results with a foreach() loop. You can read more about the RecursiveDirectoryIterator and the RecursiveIterator Iterator at http://www.php.net/~helly/php/ext/spl/. For more examples of recursively processing a directory tree, see the listings in ―6.11: Recursively Processing Directories,‖ ―6.15: Copying Directories,‖ n ―6.17: Deleting Directories,‖ and ―6.20: Searching for Files in a Directory.‖ You can also read about recursively processing arrays in the listing in ―4.3: Processing Nested Arrays.‖ 6.12 Printing Directory Trees
  • 13. Problem You want to print a hierarchical listing of a directory and its contents. Solution Write a recursive function to traverse the directory and print its contents: <pre> <?php // function to recursively process // a directory and all its subdirectories // and print a hierarchical list function printTree($dir, $depth=0) { // check if argument is a valid directory if (!is_dir($dir)) { die("Argument is not a directory!"); } // open directory handle $dh = opendir($dir) or die ("Cannot open directory"); // iterate over files in directory while (($file = readdir($dh)) !== false) { // filter out "." and ".." if ($file != "." && $file != "..") { if (is_dir("$dir/$file")) { // if this is a subdirectory (branch) // print it and go deeper echo str_repeat(" ", $depth) . " [$file]n"; printTree("$dir/$file", ($depth+1)); } else { // if this is a file (leaf) // print it echo str_repeat(" ", $depth) . " $filen"; } } } } // recursively process and print directory tree printTree('./test/'); ?> </pre> Comments This listing is actually a variant of the technique outlined in the listing in ―6.11: Recursively Processing Directories.‖ Here, a recursive function travels through the named directory and its children, printing the name of every element found. A depth counter is incremented every time the function enters a subdirectory; the str_ repeat() function uses this depth counter to pad the listing with spaces and thus simulate a hierarchical tree. For more examples of recursively processing a directory tree, see the listings in ―6.15: Copying Directories‖ and ―6.17: Deleting Directories.‖ 6.13 Copying Files Problem You want to copy a file from one location to another. Solution Use PHP’s copy() function: <?php // set file name $source = "dummy.txt"; $destination = "dummy.txt.backup"; // copy file if it exists, else exit
  • 14. if (file_exists($source)) { copy ($source, $destination) or die ("Cannot copy file '$source'"); echo "File successfully copied."; } else { die ("Cannot find file '$source'"); } ?> Comments In PHP, creating a copy of a file is as simple as calling the copy() function and passing it the source and destination file names and paths. The function returns true if the file is successfully copied. If what you really want is to create a copy of a directory, visit the listing in ―6.15: Copying Directories.‖ NOTE If the destination file already exists, it will be overwritten with no warning by copy(). If this is not what you want, implement an additional check for the target file with file_exists() and exit with a warning if the file already exists. 6.14 Copying Remote Files Problem You want to create a local copy of a file located on a remote server. Solution Use PHP’s file_get_contents() and file_put_contents() functions to read a remote file and write the retrieved data to a local file: <?php // increase script execution time limit ini_set('max_execution_time', 600); // set URL of file to be downloaded $remoteFile = "http://www.some.domain/remote.file.tgz"; // set name of local copy $localFile = "local.file.tgz"; // read remote file $data = file_get_contents($remoteFile) or die("Cannot read from remote file"); // write data to local file file_put_contents($localFile, $data) or die("Cannot write to local file"); // display success message echo "File [$remoteFile] successfully copied to [$localFile]"; ?> 210 P H P P r o g r a m m i n g S o l u t i o n s Comments Most of PHP’s file functions support reading from remote files. In this listing, this capability is exploited to its fullest to create a local copy of a remote file. The file_get_contents() function reads the contents of a remote file into a string, and the file_put_contents() function then writes this data to a local file, thereby creating an exact copy. Both functions are binary-safe, so this technique can be safely used to copy both binary and non-binary files. 6.15 Copying Directories Problem You want to copy a directory and all its contents, including subdirectories.
  • 15. Solution Write a recursive function to travel through a directory, copying files as it goes: <?php // function to recursively copy // a directory and its subdirectories function copyRecursive($source, $destination) { // check if source exists if (!file_exists($source)) { die("'$source' is not valid"); } if (!is_dir($destination)) { mkdir ($destination); } // open directory handle $dh = opendir($source) or die ("Cannot open directory '$source'"); // iterate over files in directory while (($file = readdir($dh)) !== false) { // filter out "." and ".." if ($file != "." && $file != "..") { if (is_dir("$source/$file")) { // if this is a subdirectory // recursively copy it copyRecursive("$source/$file", "$destination/$file"); } else { // if this is a file // copy it copy ("$source/$file", "$destination/$file") or die ("Cannot copy file '$file'"); } } } // close directory closedir($dh); } // copy directory recursively copyRecursive("www/template", "www/site12"); echo "Directories successfully copied."; ?> Comments This listing is actually a combination of techniques discussed in the listings in ―6.11: Recursively Processing Directories‖ and ―6.13: Copying Files.‖ Here, the custom copyRecursive() function iterates over the source directory and, depending on whether it finds a file or directory, copies it to the target directory or invokes itself recursively. The recursion ends when no further subdirectories are left to be traversed. Note that if the target directory does not exist at any stage, it is created with the mkdir() function. 6.16 Deleting Files Problem You want to delete a file. Solution Use PHP’s unlink() function: <?php // set file name $file = "shakespeare.asc"; 212 P H P P r o g r a m m i n g S o l u t i o n s // check if file exists // if it does, delete it
  • 16. if (file_exists($file)) { unlink ($file) or die("Cannot delete file '$file'"); echo "File successfully deleted."; } else { die ("Cannot find file '$file'"); } ?> Comments To delete a file with PHP, simply call the unlink() function with the file name and path. The function returns true if the file was successfully deleted. NOTE Typically, PHP will not be able to delete files owned by other users; the PHP process can only delete files owned by the user it’s running as. This is a common cause of errors, so keep an eye out for it! 6.17 Deleting Directories Problem You want to delete a directory and its contents, including subdirectories. Solution Write a recursive function to travel through a directory and its children, deleting files as it goes: <?php // function to recursively delete // a directory and its subdirectories function deleteRecursive($dir) { // check if argument is a valid directory if (!is_dir($dir)) { die("'$dir' is not a valid directory"); } // open directory handle $dh = opendir($dir) or die ("Cannot open directory '$dir'"); C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 213 // iterate over files in directory while (($file = readdir($dh)) !== false) { // filter out "." and ".." if ($file != "." && $file != "..") { if (is_dir("$dir/$file")) { // if this is a subdirectory // recursively delete it deleteRecursive("$dir/$file"); } else { // if this is a file // delete it unlink ("$dir/$file") or die ("Cannot delete file '$file'"); } } } // close directory closedir($dh); // remove top-level directory rmdir($dir); } // delete directory recursively deleteRecursive("junk/robert/"); echo "Directories successfully deleted."; ?>
  • 17. Comments In PHP, the function to remove a directory a rmdir(). Unfortunately, this function only works if the directory in question is empty. Therefore, to delete a directory, it is first necessary to iterate over it and delete all the files within it. If the directory contains subdirectories, those need to be deleted too; you do this by entering them and erasing their contents. The most efficient way to accomplish this task is with a recursive function such as the one in the previous listing, which is a combination of the techniques outlined in the listing in ―6.11: Recursively Processing Directories‖ and the listing in ―6.16: Deleting Files.‖ Here, the deleteRecursive() function accepts a directory path and name and goes to work deleting the files in it. If it encounters a directory, it invokes itself recursively to enter that directory and clean it up. Once all the contents of a directory are erased, you use the rmdir() function to remove it completely. 214 P H P P r o g r a m m i n g S o l u t i o n s 6.18 Renaming Files and Directories Problem You want to move or rename a file or directory. Solution Use PHP’s rename() function: <?php // set old and new file/directory names $oldFile = "home/john"; $newFile = "home/jane"; // check if file/directory exists // if it does, move/rename it if (file_exists($oldFile)) { rename ($oldFile, $newFile) or die("Cannot move/rename file '$oldFile'"); echo "Files/directories successfully renamed."; } else { die ("Cannot find file '$oldFile'"); } ?> Comments A corollary to PHP’s copy() function, you can use the rename() function to both rename and move files. Like copy(), rename()accepts two arguments, a source file and a destination file, and attempts to rename the former to the latter. It returns true on success. 6.19 Sorting Files Problem You want to sort a file listing. C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 215 Solution Save the file list to an array, and then use the array_multisort() function to sort it by one or more attributes: <?php // define directory $dir = "./test/a";
  • 18. // check if it is a directory if (!is_dir($dir)) { die("Argument '$dir' is not a directory!"); } // open directory handle $dh = opendir($dir) or die ("Cannot open directory '$dir'!"); // iterate over files in directory while (($file = readdir($dh)) !== false) { // filter out "." and ".." if ($file != "." && $file != "..") { // add an entry to the file list for this file $fileList[] = array("name" => $file, "size" => filesize("$dir/$file"), "date" => filemtime("$dir/$file")); } } // close directory closedir($dh); // separate all the elements with the same key // into individual arrays foreach ($fileList as $key=>$value) { $name[$key] = $value['name']; $size[$key] = $value['size']; $date[$key] = $value['date']; } // now sort by one or more keys // sort by name array_multisort($name, $fileList); print_r($fileList); 216 P H P P r o g r a m m i n g S o l u t i o n s // sort by date and then size array_multisort($date, $size, $fileList); print_r($fileList); ?> Comments Here, PHP’s directory functions are used to obtain a list of the files in a directory, and place them in a two-dimensional array. This array is then processed with PHP’s array_multisort() function, which is especially good at sorting symmetrical multidimensional arrays. The array_multisort() function accepts a series of input arrays and uses them as sort criteria. Sorting begins with the first array; values in that array that evaluate as equal are sorted by the next array, and so on. This makes it possible to sort the file list first by size and then date, or by name and then size, or any other permutation thereof. Once the file list has been sorted, it can be processed further or displayed in tabular form. 6.20 Searching for Files in a Directory Problem You want to find all the files matching a particular name pattern, starting from a toplevel search directory. Solution Write a recursive function to search the directory and its children for matching file names: <?php // function to recursively search // directories for matching filenames function searchRecursive($dir, $pattern) { // check if argument is a valid directory if (!is_dir($dir)) { die("Argument '$dir' is not a directory!"); }
  • 19. // declare array to hold matches global $matchList; C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 217 // open directory handle $dh = opendir($dir) or die ("Cannot open directory '$dir'!"); // iterate over files in directory while (($file = readdir($dh)) !== false) { // filter out "." and ".." if ($file != "." && $file != "..") { if (is_dir("$dir/$file")) { // if this is a subdirectory // recursively process it searchRecursive("$dir/$file", $pattern); } else { // if this is a file // check for a match // add to $matchList if found if (preg_match("/$pattern/", $file)) { $matchList[] = "$dir/$file"; } } } } // return the final list to the caller return $matchList; } // search for file names containing "ini" $fileList = searchRecursive("c:/windows", "ini"); print_r($fileList); ?> Comments This listing is actually a variant of the technique outlined in the listing in ―6.11: Recursively Processing Directories.‖ Here, a recursive function travels through the named directory and its children, using the preg_match() function to check each file name against the name pattern. Matching file names and their paths are stored in an array, which is returned to the caller once all subdirectories have been processed. An alternative approach here involves using the PEAR File_Find class, available from http://pear.php.net/package/File_Find. This class exposes a search() method, which accepts a search pattern and a directory path and performs a recursive search in the named directory for files matching the search pattern. The return value of the method is an array containing a list of paths to the matching files. 218 P H P P r o g r a m m i n g S o l u t i o n s Here’s an illustration of this class in action: <?php // include File_Find class include "File/Find.php"; // search recursively for file names containing "tgz" // returns array of paths to matching files $fileList = File_Find::search("tgz", "/tmp"); print_r($fileList); ?> For more examples of recursively processing a directory tree, see the listings in ―6.11: Recursively Processing Directories,‖ ―6.15: Copying Directories,‖ and ―6.17: Deleting Directories.‖ 6.21 Searching for Files in PHP’s
  • 20. Default Search Path Problem You want to check if a particular file exists in PHP’s default search path, and obtain the full path to it. Solution Scan PHP’s include_path for the named file and, if found, obtain the full path to it with PHP’s realpath() function: <?php // function to check for a file // in the PHP include path function searchIncludePath($file) { // get a list of all the directories // in the include path $searchList = explode(";", ini_get('include_path')); C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 219 // iterate over the list // check for the file // return the path if found foreach ($searchList as $dir) { if (file_exists("$dir/$file")) { return realpath("$dir/$file"); } } return false; } // look for the file "DB.php" $result = searchIncludePath('DB.php'); echo $result ? "File was found in $result" : "File was not found"; ?> Comments A special PHP variable defined through the php.ini configuration file, the include_ path variable typically contains a list of directories that PHP will automatically look in for files include-d or require-d by your script. It is similar to the Windows $PATH variable, or the Perl @INC variable. In this listing, the directory list stored in this variable is read into the PHP script with the ini_get() function, and a foreach() loop is then used to iterate over the list and check if the file exists. If the file is found, the realpath() function is used to obtain the full file system path to the file. 6.22 Searching and Replacing Patterns Within Files Problem You want to perform a search/replace operation within one or more files. Solution Use PEAR’s File_SearchReplace class: <?php // include File_SearchReplace class include "File/SearchReplace.php"; 220 P H P P r o g r a m m i n g S o l u t i o n s // initialize object $fsr = new File_SearchReplace('PHP', 'PHP: Hypertext Pre-Processor', array('chapter_01.txt',
  • 21. 'chapter_02.txt')); // perform the search // write the changes to the file(s) $fsr->doReplace(); // get the number of matches echo $fsr->getNumOccurences() . " match(es) found."; ?> Comments To perform search-and-replace operations with one or more files, you’ll need the PEAR File_SearchReplace class, available from http://pear.php.net/ package/File_SearchReplace. Using this class, it’s easy to replace patterns inside one or more files. The object constructor requires three arguments: the search term, the replacement text, and an array of files to search in. The search/replace operation is performed with the doReplace() method, which scans each of the named files for the search term and replaces matches with the replacement text. The total number of matches can always be obtained with the getNumOccurences() method. TIP You can use regular expressions for the search term, and specify an array of directories (instead of files) as an optional fourth argument to the object constructor. It’s also possible to control whether the search function should comply with Perl or PHP regular expression matching norms. More information on how to accomplish these tasks can be obtained from the class documentation and source code. 6.23 Altering File Extensions Problem You want to change all or some of the file extensions in a directory. C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 221 Solution Use PHP’s glob() and rename() functions: <?php // define directory path $dir = './test'; // define old and new extensions $newExt = "asc"; $oldExt = "txt"; // search for files matching pattern foreach (glob("$dir/*.$oldExt") as $file) { $count++; // extract the file name (without the extension) $name = substr($file, 0, strrpos($file, ".")); // rename the file using the name and new extension rename ($file, "$name.$newExt") or die ("Cannot rename file '$file'!"); } echo "$count file(s) renamed."; ?> Comments PHP’s glob() function builds a list of files matching a particular pattern, and
  • 22. returns an array with this information. It’s then a simple matter to iterate over this array, extract the filename component with substr(), and rename the file with the new extension. 6.24 Finding Differences Between Files Problem You want to perform a UNIX diff on two files. 222 P H P P r o g r a m m i n g S o l u t i o n s Solution Use PEAR’s Text_Diff class: <pre> <?php // include Text_Diff class include "Text/Diff.php"; include "Text/Diff/Renderer.php"; include "Text/Diff/Renderer/unified.php"; // define files to compare $file1 = "rhyme1.txt"; $file2 = "rhyme2.txt"; // compare files $diff = &new Text_Diff(file($file1), file($file2)); // initialize renderer and display diff $renderer = &new Text_Diff_Renderer_unified(); echo $renderer->render($diff); ?> </pre> Comments The UNIX diff program is a wonderful way of quickly identifying differences between two strings. PEAR’s Text_Diff class, available from http://pear.php .net/package/Text_Diff, brings this capability to PHP, making it possible to easily compare two strings and returning the difference in standard diff format. The input arguments to the Text_Diff object constructor must be two arrays of string values. Typically, these arrays contain the lines of the files to be compared, and are obtained with the file() function. The Text_Diff_Renderer class takes care of displaying the comparison in UNIX diff format, via the render() method of the object. As an illustration, here’s some sample output from this listing: @@ -1,2 +1,3 @@ They all ran after the farmer's wife, -Who cut off their tales with a carving knife. +Who cut off their tails with a carving knife, +Did you ever see such a thing in your life? C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 223 6.25 “Tailing” Files Problem You want to ―tail‖ a file, or watch it update in real time, on a Web page. Solution Display the output of the UNIX tail program on an auto-refreshing Web page: <html> <head> <meta http-equiv="refresh" content="5;url=<?=$_SERVER['PHP_SELF']?>">
  • 23. </head> <body> <pre> <?php // set name of log file $file = "/tmp/rootproc.log"; // set number of lines to tail $limit = 10; // run the UNIX "tail" command and display the output system("/usr/bin/tail -$limit $file"); ?> </pre> </body> </html> Comments UNIX administrators commonly use the tail program to watch log files update in real time. A common requirement in Web applications, especially those that interact with system processes, is to have this real-time update capability available within the application itself. The simplest—though not necessarily most elegant—way to do this is to use PHP’s system() function to fork an external tail process and display its output on a Web page. A <meta http-equiv="refresh" ... /> tag at the top of the page causes it to refresh itself every few seconds, thereby producing an almost real-time update. 224 P H P P r o g r a m m i n g S o l u t i o n s NOTE Forking an external process from PHP is necessarily a resource-intensive process. To avoid excessive usage of system resources, tune the page refresh interval in this listing to correctly balance the requirements of real-time monitoring and system resource usage. 6.26 Listing Available Drives or Mounted File Systems Problem You want a list of available drives (Windows) or mounted file systems (UNIX). Solution Use the is_dir() function to check which drive letters are valid (Windows): <?php // loop from "a" to "z" // check which are active drives // place active drives in an array foreach(range('a','z') as $drive) { if (is_dir("$drive:")) { $driveList[] = $drive; } } // print array of active drive letters print_r($driveList); ?> Read the /etc/mtab file for a list of active mount points (UNIX): <?php // read mount information from mtab file $lines = file("/etc/mtab") or die ("Cannot read file"); // iterate over lines in file // get device and mount point
  • 24. // add to array foreach ($lines as $line) { C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 225 $arr = explode(" ", $line); $mountList[$arr[0]] = $arr[1]; } // print array of active mounts print_r($mountList); ?> Comments For Web applications that interact with the file system—for example, an interactive file browser or disk quota manager—a common requirement involves obtaining a list of valid system drives (Windows) or mount points (UNIX). The previous listings illustrate simple solutions to the problem. Windows drive letters always consist of a single alphabetic character. So, to find valid drives, use the is_dir() function to test the range of alphabetic characters, from A to Z, and retain those for which the function returns true. A list of active UNIX mounts is usually stored in the system file /etc/mtab (although your UNIX system may use another file, or even the /proc virtual file system). So, to find valid drives, simply read this file and parse the information within it. 6.27 Calculating Disk Usage Problem You want to calculate the total disk space used by a disk partition or directory. Solution Use PHP’s disk_free_space() and disk_total_space() functions to calculate the total disk usage for a partition: <?php // define partition // for example, "C:" for Windows // or "/" for UNIX $dir = "c:"; // get free space in MB $free = round(disk_free_space($dir)/1048576); 226 P H P P r o g r a m m i n g S o l u t i o n s // get total available space in MB $total = round(disk_total_space($dir)/1048576); // calculate used space in MB $used = $total - $free; echo "$used MB used"; ?> Write a recursive function to calculate the total disk space consumed by a particular directory: <?php // function to recursively process // a directory and all its subdirectories function calcDirUsage($dir) { // check if argument is a valid directory if (!is_dir($dir)) { die("Argument '$dir' is not a directory!"); } // declare variable to hold running total global $byteCount; // open directory handle $dh = opendir($dir) or die ("Cannot open directory '$dir'!"); // iterate over files in directory
  • 25. while (($file = readdir($dh)) !== false) { // filter out "." and ".." if ($file != "." && $file != "..") { if (is_dir("$dir/$file")) { // if this is a subdirectory // recursively process it calcDirUsage("$dir/$file"); } else { // if this is a file // add its size to the running total $byteCount += filesize("$dir/$file"); } } } // return the final list to the caller return $byteCount; } C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 227 // calculate disk usage for directory in MB $bytes = calcDirUsage("c:/windows"); $used = round($bytes/1048576); echo "$used MB used"; ?> Comments PHP’s disk_total_space() and disk_free_space() functions return the maximum and available disk space for a particular drive or partition respectively, in bytes. Subtracting the latter from the former returns the number of bytes currently in use on the partition. Obtaining the disk space used by a specific directory and its subdirectories is somewhat more complex. The task here involves adding the sizes of all the files in that directory and its subdirectories to arrive at a total count of bytes used. The simplest way to accomplish this is with a recursive function such as the one outlined in the previous listing, where file sizes are calculated and added to a running total. Directories are deal with recursively, in a manner reminiscent of the technique outlined in the listing in ―6.11: Recursively Processing Directories.‖ The final sum will be the total bytes consumed by the directory and all its contents (including subdirectories). TIP To convert byte values to megabyte or gigabyte values for display, divide by 1048576 or 1073741824 respectively. 6.28 Creating Temporary Files Problem You want to create a temporary file with a unique name, perhaps as a flag or semaphore for other processes. Solution Use PHP’s tempnam() function: <?php // create temporary file with prefix "tmp" $filename = tempnam("/tmp", "tmp"); echo "Temporary file [$filename] successfully created"; ?> 228 P H P P r o g r a m m i n g S o l u t i o n s Comments
  • 26. PHP’s tempnam() function accepts two arguments, a directory name and a file prefix, and attempts to create a file using the prefix and a randomly generated identifier in the specified directory. If the file is successfully created, the function returns the complete path and name to it—this can then be used by other file functions to write data to it. This listing offers an easy way to quickly create a file for temporary use, perhaps as a signal to other processes. Note, however, that the file created by tempnam() must be manually deleted with unlink() once it’s no longer required. TIP PHP’s tmpfile() function creates a unique, temporary file that exists only for the duration of the script. Read more about this function at http://www.php.net/tmpfile. 6.29 Finding the System Temporary Directory Problem You want to retrieve the path to the system’s temporary directory. Solution Use PHP’s tempnam() function to create a temporary file, and then obtain the path to it: <?php // create a temporary file and get its name // result: "Temporary directory is /tmp" $tmpfile = tempnam("/this/directory/does/not/exist", "tmp"); unlink ($tmpfile); echo "Temporary directory is " . dirname($tmpfile); ?> Comments The tempnam() function provides an easy way to create a temporary file on the system. Such a file is typically used as a semaphore or flag for other processes—for example, it can be used for file locking processes or status indicators. The return value of the tempnam() function is the full path to the newly minted file. Given this C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 229 file is always created in the system’s temporary directory, running the dirname() function on the complete file path produces the required information. NOTE In this listing, the first parameter to tempnam() is a nonexistent directory path. Why? Well, tempnam() normally requires you to specify the directory in which the temporary file is to be created. Passing a nonexistent directory path forces the function to default to the system’s temporary directory, thereby making it possible to identify the location. An alternative approach consists of using the PEAR File_Util class, available at http://pear.php.net/package/File. This class exposes a tmpDir() method, which returns the path to the system temporary directory. Take a look: <?php // include File_Util class include "File/Util.php"; // get name of system temporary directory // result: "Temporary directory is /tmp" $tmpdir = File_Util::tmpDir(); echo "Temporary directory is $tmpdir"; ?> 6.30 Converting Between Relative
  • 27. and Absolute File Paths Problem You want to convert a relative path to an absolute path. Solution Use PHP’s realpath() function: <?php // result: "/usr/local/apache/htdocs" (example) echo realpath("."); 230 P H P P r o g r a m m i n g S o l u t i o n s // result: "/usr/local/apache" (example) echo realpath(".."); // result: "/usr/local/" echo realpath("/usr/local/mysql/data/../.."); ?> Comments To convert a relative path to an absolute file path, use PHP’s realpath() function. This function performs ―path math,‖ translating all the relative locations in a path string to return a complete absolute file path. NOTE On a tangential note, take a look at PEAR’s File_Util class, available from http://pear .php.net/package/File, which comes with a method to calculate the difference between two file paths. The following simple example illustrates: <?php // include File_Util class include "File/Util.php"; // define two locations on the filesystem $begin = "/usr/local/apache"; $end = "/var/spool/mail"; // figure out how to get from one to the other // result: "../../../var/spool/mail" echo File_Util::relativePath($end, $begin, "/"); ?> 6.31 Parsing File Paths Problem You want to extract the path, file name, or extension path from a file path. C h a p t e r 6 : Wo r k i n g w i t h F i l e s a n d D i r e c t o r i e s 231 Solution Use PHP’s pathinfo() function to automatically split the file path into its constituent parts: <?php // define path and filename $path = "/etc/sendmail.cf"; // decompose path into constituents $data = pathinfo($path); print_r($data); ?> Comments The pathinfo() function is one of PHP’s more useful path manipulation functions. Pass it a file path, and pathinfo() will split it into its individual components. The
  • 28. resulting associative array contains separate keys for directory name, file name, and file extension. You can then easily access and use these keys for further processing— for example, the variable $data['dirname'] will return the value /etc. TIP When parsing file paths, also consider using the basename(), dirname(), and realpath() functions. You can read about these functions in the PHP manual at http:// www.php.net/filesystem. From PHP Solutions Dynamic Web Design Made Easy, Second Edition.pdf Chapter 7:Using PHP to Manage Files PHP has a huge range of functions designed to work with the server�s file system, but finding the right one for the job isn�t always easy. This chapter cuts through the tangle to show you some practical uses of these functions, such as reading and writing text files to store small amounts of information without a database. Loops play an important role in inspecting the contents of the file system, so you�ll also explore some of the Standard PHP Library (SPL) iterators that are designed to make loops more efficient. As well as opening local files, PHP can read public files, such as news feeds, on other servers. News feeds are normally formatted as XML (Extensible Markup Language). In the past, extracting information from an XML file was tortuous process, but that�s no longer the case thanks to the very aptly named SimpleXML that was introduced in PHP 5. In this chapter, I�ll show you how to create a drop-down menu that lists all images in a folder, create a function to select files of a particular type from a folder, pull in a live news feed from another server, and prompt a visitor to download an image or PDF file rather than open it in the browser. As an added bonus, you�ll learn how to change the time zone of a date retrieved from another website. This chapter covers the following subjects: Reading and writing files
  • 29. Listing the contents of a folder Inspecting files with the SplFileInfo class Controlling loops with SPL iterators Using SimpleXML to extract information from an XML file Consuming an RSS feed Creating a download link Checking that PHP has permission to open a file As I explained in the previous chapter, PHP runs on most Linux servers as nobody or apache. Consequently, a folder must have minimum access permissions of 755 for scripts to open a file. To create or alter files, you normally need to set global access permissions of 777, the least secure setting. If PHP is configured to run in your own name, you can be more restrictive, because your scripts can create and write to files in any folder for which you have read, write, and execute permissions. On a Windows server, you need write permission to create or update a file. If you need assistance with changing permissions, consult your hosting company. Configuration settings that affect file access Hosting companies can impose further restrictions on file access through php.ini. To find out what restrictions have been imposed, run phpinfo() on your website, and check the settings in the Core section. Table 7-1 lists the settings you need to check. Unless you run your own server, you normally have no control over these settings. Table 7-1. PHP configuration settings that affect file access Directive Default value Description allow_url_fopen On Allows PHP scripts to open public files on the Internet. allow_url_include Off Controls the ability to include remote files. open_basedir no value Restricts accessible files to the specified directory tree. Even if no value is set, restrictions may be set directly in the server configuration. safe_mode Off Mainly restricts the ability to use certain functions (for details, see www.php.net/manual/en/features.safemode. functions.php). This feature has been deprecated since PHP 5.3 and will be removed at a future date. safe_mode_include_dir no value If safe_mode is enabled, user and group ID checks are skipped when files are included from the specified directory tree. Accessing remote files Arguably the most important setting in Table 7-1 is allow_url_fopen. If it�s disabled, you cannot access useful external data sources, such as news feeds and public XML documents. Prior to PHP 5.2, allow_url_fopen also allowed you to include remote files in your pages. This represented a major security risk, prompting many hosting companies to disabled allow_url_fopen. The security risk was eliminated in PHP 5.2 by the introduction of a separate setting for including remote files: allow_url_include, which is disabled by default. After PHP 5.2 was released, not all hosting companies realized that allow_url_fopen had changed, and continued to disable it. Hopefully, by the time you read this, the message will have sunk in that allow_url_fopen allows you to read remote files, but not to include them directly in your scripts. If your hosting company still disables allow_url_fopen, ask it to turn it on. Otherwise, you won�t be able to use PHP Solution 7-5. If the hosting company refuses, you should consider moving to one with a better understanding of PHP. Configuration settings that affect local file access If the Local Value column for open_basedir or safe_mode_include_dir displays no value, you can ignore this section. However, if it does have a value, the meaning depends on whether the value ends with a trailing slash, like this: /home/includes/ In this example, you can open or include files only from the includes directory or any of its subdirectories. If the value doesn�t have a trailing slash, the value after the last slash acts as a prefix. For example, /home/inc gives you access to /home/inc, /home/includes, /home/incredible, and so on— assuming, of course, that they exist or you have the right to create them. PHP Solution 7-1 shows what happens when you try to access a file outside the limits imposed by open_basedir. Creating a file storage folder for local testing Storing data inside your site root is highly insecure, particularly if you need to set global access
  • 30. permissions on the folder. If you have access to a private folder outside the site root, create your data store as a subfolder and give it the necessary permissions. For the purposes of this chapter, I suggest that Windows users create a folder called private on their C drive. Mac users should create a private folder inside their home folder and then set Read & Write permissions in the folder�s Info panel as described in the previous chapter. Reading and writing files The restrictions described in the previous section reduce the attraction of reading and writing files with PHP. Using a database is more convenient and offers greater security. However, that assumes you have access to a database and the necessary knowledge to administer it. So, for small-scale data storage and retrieval, working directly with text files is worth considering. Reading files in a single operation The simplest way to read the contents of a text file is to use file_get_contents() or readfile(). PHP Solution 7-1: Getting the contents of a text file This PHP solution demonstrates how to use file_get_contents() and readfile(), and explains how they differ. 1. Create a text file in your private folder, type some text into it, and save it as filetest_01.txt (or use the version in the ch07 folder). 2. Create a new folder called filesystem in your phpsols site root, and create a PHP file called get_contents.php in the new folder. Insert the following code inside a PHP block (get_contents_01.php in the ch07 folder shows the code embedded in a web page, but you can use just the PHP code for testing purposes): echo file_get_contents('C:/private/filetest_01.txt'); If you�re on a Mac, amend the pathname like this, using your own Mac username: echo file_get_contents('/Users/username/private/filetest_01.txt'); If you�re testing on a remote server, amend the pathname accordingly. For brevity, the remaining examples in this chapter show only the Windows pathname. 3. Save get_contents.php, and view it in a browser. Depending on what you wrote in filetest_01.txt, you should see something like the following screenshot. You shouldn�t see any error messages on your local system, unless you typed the code incorrectly or you didn�t set the correct permissions on a Mac. However, on a remote system, you may see error messages similar to this: The error messages in the preceding screenshot were created on a local system to demonstrate what happens when open_basedir has been set either in php.ini or on the server. They mean you�re trying to access a file outside your permitted file structure. The first error message should indicate the allowed paths. On a Windows server, each path is separated by a semicolon. On Linux, the separator is a colon. 4. Change the code in get_contents.php like this (it�s in get_contents_02.php): readfile('C:/private/filetest_01.txt'); 5. Save get_contents.php, and reload it in your browser. The contents of the text file are displayed as before. So, what�s the difference? The original code uses echo to display the contents of the file. The amended code doesn�t use echo. In other words, file_get_contents() simply gets the contents of a file, but readfile() also displays it immediately. The advantage of file_get_contents() is that you can assign the file contents to a variable and process it in some way before deciding what to do with it. 6. Change the code in get_contents.php like this (or use get_contents_03.php), and load the page into a browser: // get the contents of the file $contents = file_get_contents('C:/private/filetest_01.txt'); // split the contents into an array of words $words = explode(' ', $contents); // extract the first four elements of the array $first = array_slice($words, 0, 4); // join the first four elements and display echo implode(' ', $first); This stores the contents of filetest_01.txt in a variable, which is passed to the explode() function. This alarmingly named function ―blows apart‖ a string and converts it into an array, using the first argument to determine where to break the string. In this case, a space is used, so the contents of the text file are split into an array of words. The array of words is then passed to the array_slice() function, which takes a slice out of
  • 31. an array starting from the position specified in the second argument. The third argument specifies the length of the slice. PHP counts arrays from 0, so this extracts the first four words. Finally, implode() does the opposite of explode(), joining the elements of an array and inserting the first argument between each one. The result is displayed by echo, producing the following outcome: Instead of displaying the entire contents of the file, the script now displays only the first four words. The full string is still stored in $contents. 7. If you need to extract the first few words from a string on a regular basis, you could create a custom function like this: function getFirstWords($string, $number) { $words = explode(' ', $string); $first = array_slice($words, 0, $number); return implode(' ', $first); } You can extract the first seven words like this (the code is in get_contents_04.php): $contents = file_get_contents('C:/private/filetest_01.txt'); echo getFirstWords($contents, 7); 8. Among the dangers with accessing external files are that the file might be missing or its name misspelled. Change the code like this (it�s in get_contents_05.php): $contents = file_get_contents('C:/private/filetest_01.txt'); if ($contents === false) { echo 'Sorry, there was a problem reading the file.'; } else { echo $contents; } If the file_get_contents() function can�t open the file, it returns false. Often, you can test for false by using the logical Not operator like this: if (!$contents) { If the file is empty or contains only the number 0, $contents is implicitly false. To make sure the returned value is explicitly false, you need to use the identical operator (three equal signs). 9. Test the page in a browser, and it should display the contents of the text file as before. 10. Replace the contents of filetest_01.txt with the number 0. Save the text file, and reload get_contents.php in the browser. The number displays correctly. 11. Delete the number in the text file, and reload get_contents.php. You should get a blank screen, but no error message. The file loads, but doesn�t contain anything. 12. Change the code in get_contents.php so that it attempts to load a nonexistent file. When you load the page, you should see an ugly error message like this: ―Failed to open stream‖ means it couldn�t open the file. USING PHP TO MANAGE FILES 185 13. This is an ideal place to use the error control operator (see Chapter 4). Insert an @ mark immediately in front of the call to file_get_contents() like this (the code is in get_contents_07.php): $contents = @ file_get_contents('C:/private/filetest0.txt'); 14. Test get_contents.php in a browser. You should now see only the following custom error message: Always add the error control operator only after testing the rest of a script. When developing, you need to see error messages to understand why something isn�t working the way you expect. Text files can be used as a flat-file database—where each record is stored on a separate line, with a tab, comma, or other delimiter between each field (see http://en.wikipedia.org/ wiki/Flat_file_database). With this sort of file, it�s more convenient to store each line individually in an array to process with a loop. The file() function builds the array automatically. PHP Solution 7-2: Reading a text file into an array To demonstrate the file() function, this PHP solution uses filetest_02.txt, which contains just two lines as follows: david, codeslave chris, bigboss This will be used as the basis for a simple login system to be developed further in Chapter 9. 1. Create a PHP file called file.php inside the filesystem folder. Insert the following code (or use file_01.php from the ch07 folder):
  • 32. <?php // read the file into an array called $users $users = file('C:/private/filetest_02.txt'); ?> <pre> <?php print_r($users); ?> </pre> This draws the contents of filetest_02.txt into an array called $users and then passes it to print_r() to display the contents of the array. The <pre> tags simply make the output easier to read in a browser. 2. Save the page, and load it in a browser. You should see the following output: It doesn�t look very exciting, but now that each line is a separate array element, you can loop through the array to process each line individually. 3. You need to use a counter to keep track of each line; a for loop is the most convenient (see ―The versatile for loop‖ in Chapter 3). To find out how many times the loop should run, pass the array to the count() function to get its length. Amend the code in file.php like this (or use file_02.php): <?php // read the file into an array called $users $users = file('C:/private/filetest03.txt'); // loop through the array to process each line for ($i = 0; $i < count($users); $i++) { // separate each element and store in a temporary array $tmp = explode(', ', $users[$i]); // assign each element of the temporary array to a named array key $users[$i] = array('name' => $tmp[0], 'password' => $tmp[1]); } ?> <pre> <?php print_r($users); ?> </pre> The count() function returns the length of an array, so in this case, the value of count($users) is 2. This means the first line of the loop is equivalent to this: for ($i = 0; $i < 2; $i++) { The loop continues running while $i is less than 2. Since arrays are always counted from 0, this means the loop runs twice before stopping. Inside the loop, the current array element ($users[$i]) is passed to the explode() function. In this case, the separator is defined as a comma followed by a space (', '). However, you can use any character or sequence of characters: using "t" (see Table 3-4 in Chapter 3) as the first argument to explode() turns a tab-separated string into an array. USING PHP TO MANAGE FILES 187 The first line in filetest 02.txt looks like this: david, codeslave When this line is passed to explode(), the result is saved in $tmp, so $tmp[0] is david, and $tmp[1] is codeslave. The final line inside the loop reassigns $tmp[0] to $users[0]['name'], and $tmp[1] to $users[0]['password']. The next time the loop runs, $tmp is reused, and $users[1]['name'] becomes chris, and $users[1]['password'] becomes bigboss. 4. Save file.php, and view it in a browser. The result looks like this: 5. Take a close look at the gap between codeslave and the closing parenthesis of the first subarray. The file() function doesn�t remove newline characters or carriage returns, so you need to do it yourself. Pass the final item of $tmp to rtrim() like this: $users[$i] = array('name' => $tmp[0], 'password' => rtrim($tmp[1])); The rtrim() function removes whitespace (spaces, tabs, newline characters, and carriage returns) from the end of a string. It has two companions: ltrim() which removes whitespace from the beginning of a string, and trim(), which removes whitespace from both ends of a string. If you�re working with each line as a whole, pass the entire line to rtrim(). 6. As always, you need to check that the file is accessible before attempting to process its contents, so wrap the main PHP block in a conditional statement like this (see file_03.php): $textfile = 'C:/private/filetest_02.txt';
  • 33. if (file_exists($textfile) && is_readable($textfile)) { // read the file into an array called $users $users = file($textfile); // loop through the array to process each line for ($i = 0; $i < count($users); $i++) { // separate each element and store in a temporary array $tmp = explode(', ', $users[$i]); // assign each element of the temporary array to a named array key $users[$i] = array('name' => $tmp[0], 'password' => rtrim($tmp[1])); } } else { echo "Can't open $textfile"; } To avoid typing out the file pathname each time, begin by storing it in a variable. This simple script extracts a useful array of names and associated passwords. You could also use this with a series of sports statistics or any data that follows a regular pattern. Opening and closing files for read/write operations The functions we have looked at so far do everything in a single pass. However, PHP also has a set of functions that allow you to open a file, read it and/or write to it, and then close the file. The following are the most important functions used for this type of operation: fopen(): Opens a file fgets(): Reads the contents of a file, normally one line at a time fread(): Reads a specified amount of a file fwrite(): Writes to a file feof(): Determines whether the end of the file has been reached rewind(): Moves an internal pointer back to the top of the file fclose(): Closes a file The first of these, fopen(), is the most difficult to understand, mainly because you need to specify how the file is to be used once it�s open: fopen() has one read-only mode, three write-only modes, and four read/write modes. Sometimes, you want to overwrite the existing content. At other times, you may want to append new material. At yet other times, you may want PHP to create a file if it doesn�t already exist. The other thing you need to understand is where each mode places the internal pointer when it opens the file. It�s like the cursor in a word processor: PHP starts reading or writing from wherever the pointer happens to be when you call fread() or fwrite(). Table 7-2 brings order to the confusion. USING PHP TO MANAGE FILES 189 Table 7-2. Read/write modes used with fopen() Type Mode Description Read-only r Internal pointer initially placed at beginning of file. Write-only w Existing data deleted before writing. Creates a file if it doesn�t already exist. a Append mode. New data added at end of file. Creates a file if it doesn�t already exist. x Creates a file only if it doesn�t already exist, so no danger of deleting existing data. r+ Read/write operations can take place in either order and begin wherever the internal pointer is at the time. Pointer initially placed at beginning of file. File must already exist for operation to succeed. w+ Existing data deleted. Data can be read back after writing. Creates a file if it doesn�t already exist. a+ Opens a file ready to add new data at end of file. Also permits data to be read back after internal pointer has been moved. Creates a file if it doesn�t already exist. Read/write x+ Creates a new file, but fails if a file of the same name already exists. Data can be read back after writing. Choose the wrong mode, and you could end up deleting valuable data. You also need to be careful about the position of the internal pointer. If the pointer is at the end of the file, and you try to read the contents, you end up with nothing. On the other hand, if the pointer is at the beginning of the file, and you start writing, you overwrite the equivalent amount of any existing data. ―Moving the internal pointer‖ later in this
  • 34. chapter explains this in more detail with a practical example. You work with fopen() by passing it the following two arguments: The path to the file you want to open One of the modes listed in Table 7-2 The fopen() function returns a reference to the open file, which can then be used with any of the other read/write functions. So, this is how you would open a text file for reading: $file = fopen('C:/private/filetest_02.txt', 'r'); Thereafter, you pass $file as the argument to other functions, such as fgets() and fclose(). Things should become clearer with a few practical demonstrations. Rather than building the files yourself, you�ll probably find it easier to use the files in the ch07 folder. I�ll run quickly through each mode. CHAPTER 7 190 Mac users need to adjust the path to the private folder in the example files to match their setup. Reading a file with fopen() The file fopen_read.php contains the following code: // store the pathname of the file $filename = 'C:/private/filetest_02.txt'; // open the file in read-only mode $file = fopen($filename, 'r'); // read the file and store its contents $contents = fread($file, filesize($filename)); // close the file fclose($file); // display the contents echo nl2br($contents); If you load this into a browser, you should see the following output: Unlike file_get_contents(), the function fread() needs to know how much of the file to read. So you need to supply a second argument indicating the number of bytes. This can be useful if you want, say, only the first 100 characters of a text file. However, if you want the whole file, you need to pass the file�s pathname to filesize() to get the correct figure. The nl2br() function in the final line converts new line characters to HTML <br /> tags. The other way to read the contents of a file with fopen() is to use the fgets() function, which retrieves one line at a time. This means that you need to use a while loop in combination with feof() to read right through to the end of the file. This is done by replacing this line: $contents = fread($file, filesize($filename)); with this (the full script is in fopen_readloop.php): // create variable to store the contents $contents = ''; // loop through each line until end of file while (!feof($file)) { // retrieve next line, and add to $contents $contents .= fgets($file); } USING PHP TO MANAGE FILES 191 The while loop uses fgets() to retrieve the contents of the file one line at a time—!feof($file) is the same as saying until the end of $file—and stores them in $contents. Both methods are more long-winded than file() or file_get_contents(). However, you need to use either fread() or fgets() if you want to read and write to a file at the same time. Replacing content with fopen() The first of the write-only modes (w) deletes any existing content in a file, so it�s useful for working with files that need to be updated frequently. You can test the w mode with fopen_write.php, which has the following PHP code above the DOCTYPE declaration: <?php // if the form has been submitted, process the input text if (isset($_POST['contents'])) { // open the file in write-only mode $file = fopen('C:/private/filetest_03.txt', 'w'); // write the contents fwrite($file, $_POST['contents']); // close the file
  • 35. fclose($file); } ?> There�s no need to use a loop this time: you�re just writing the value of $contents to the opened file. The function fwrite() takes two arguments: the reference to the file and whatever you want to write to it. In other books or scripts on the Internet, you may come across fputs() instead of fwrite(). The two functions are identical: fputs() is a synonym for fwrite(). If you load fopen_write.php into a browser, type something into the text area, and click Write to file, PHP creates filetest_03.txt and inserts whatever you typed into the text area. Since this is just a demonstration, I�ve omitted any checks to make sure that the file was successfully written. Open filetest_03.txt to verify that your text has been inserted. Now, type something different into the text area and submit the form again. The original content is deleted from filetest_03.txt and replaced with the new text. The deleted text is gone forever. Appending content with fopen() The append mode is one of the most useful ways of using fopen(), because it adds new content at the end, preserving any existing content. The main code in fopen_append.php is the same as fopen_write.php, apart from those elements highlighted here in bold: // open the file in append mode $file = fopen('C:/private/filetest_03.txt', 'a'); // write the contents after inserting new line fwrite($file, PHP_EOL . $_POST['contents']); // close the file fclose($file); CHAPTER 7 192 Notice that I have concatenated PHP_EOL to the beginning of $_POST['contents'] . This is a PHP constant that represents a new line on any operating system. On Windows, new lines require a carriage return and newline character, but Macs traditionally use only a carriage return, while Linux uses only a newline character. PHP_EOL gets round this nightmare by automatically choosing the correct characters depending on the server�s operating system. If you load fopen_append.php into a browser and insert some text, it should now be added to the end of the existing text, as shown in the following screenshot. This is a very easy way of creating a flat-file database. We�ll come back to append mode in Chapter 9. Writing a new file with fopen() Although it can be useful to have a file created automatically with the same name, it may be exactly the opposite of what you want. To make sure you�re not overwriting an existing file, you can use fopen() with x mode. The main code in fopen_exclusive.php looks like this (changes are highlighted in bold): // create a file ready for writing only if it doesn't already exist $file = fopen('C:/private/filetest_04.txt', 'x'); // write the contents fwrite($file, $_POST['contents']); // close the file fclose($file); If you load fopen_exclusive.php into a browser, type some text, and click Write to file, the content should be written to filetest_04.txt in your target folder. If you try it again, you should get a series of error messages telling you that the file already exists. Combined read/write operations with fopen() By adding a plus sign (+) after any of the previous modes, the file is opened for both reading and writing. You can perform as many read or write operations as you like—and in any order—until the file is closed. The difference between the combined modes is as follows: r+: The file must already exist; a new one will not be automatically created. The internal pointer is placed at the beginning, ready for reading existing content. w+: Existing content is deleted, so there is nothing to read when the file is first opened. a+: The file is opened with the internal pointer at the end, ready to append new material, so the pointer needs to be moved back before anything can be read. x+: Always creates a new file, so there�s nothing to read when the file is first opened. Reading is done with fread() or fgets() and writing with fwrite() exactly the same as before. What�s important is to understand the position of the internal pointer. USING PHP TO MANAGE FILES 193
  • 36. Moving the internal pointer Reading and writing operations always start wherever the internal pointer happens to be, so you normally want it to be at the beginning of the file for reading, and at the end of the file for writing. To move the pointer to the beginning of a file, pass the file reference to rewind() like this: rewind($file); Moving the pointer to the end of a file is more complex. You need to use fseek(), which moves the pointer to a location specified by an offset and a PHP constant. The constant that represents the end of the file is SEEK_END, so an offset of 0 bytes places the pointer at the end. You also need to pass fseek() a reference to the open file, so all three arguments together look like this: fseek($file, 0, SEEK_END); SEEK_END is a constant, so it doesn�t need quotes, and it must be in uppercase. You can also use fseek() to move the internal pointer to a specific position or relative to its current position. For details, see http://docs.php.net/manual/en/function.fseek.php. The file fopen_pointer.php uses the fopen() r+ mode to demonstrate combining several read and write operations, and the effect of moving the pointer. The main code looks like this: $filename = 'C:/private/filetest_04.txt'; // open a file for reading and writing $file = fopen($filename, 'r+'); // the pointer is at the beginning, so existing content is overwritten fwrite($file, $_POST['contents'] ); // read the contents from the current position $readRest = ''; while (!feof($file)) { $readRest .= fgets($file); } // reset internal pointer to the beginning rewind($file); // read the contents from the beginning (nasty gotcha here) $readAll = fread($file, filesize($filename)); // pointer now at the end, so write the form contents again fwrite($file, $_POST['contents']); // read immediately without moving the pointer $readAgain = ''; while (!feof($file)) { $readAgain .= fgets($file); } CHAPTER 7 194 // close the file fclose($file); The version of this file in the ch07 folder contains code that displays the values of $readRest, $readAll, and $readAgain to show what happens at each stage of the read/write operations. The existing content in filetest_04.txt was This works only the first time. When I typed New content. in fopen_pointer.php and clicked Write to file, I got the results shown here: Table 7-3 describes the sequence of events. Table 7-3. Sequence of read/write operations in fopen_pointer.php Command Position of pointer Result $file = fopen($filename,'r+'); Beginning of file File opened for processing fwrite($file, $_POST['contents']); End of write operation Form contents overwrites beginning of existing content while (!feof($file)) { $readRest .= fgets($file); } End of file Remainder of existing content read rewind($file); Beginning of file Pointer moved back to beginning of file $readAll = fread($file, � filesize($filename)); See text Content read from beginning of file fwrite($file, $_POST['contents']); At end of previous operation
  • 37. Form contents added at current position of pointer while (!feof($file)) { $readAgain .= fgets($file); } End of file Nothing read because pointer was already at end of file fclose($file); Not applicable File closed and all changes saved USING PHP TO MANAGE FILES 195 When I opened filetest_04.txt, this is what it contained: If you study the code in fopen_pointer.php, you�ll notice that the second read operation uses fread(). It works perfectly with this example but contains a nasty surprise. Change the code in fopen_pointer.php to add the following line after the external file has been opened (it�s commented out in the download version): $file = fopen($filename, 'r+'); fseek($file, 0, SEEK_END); This moves the pointer to the end of the file before the first write operation. Yet, when you run the script, fread() ignores the text added at the end of the file. This is because the external file is still open, so filesize() reads its original size. Consequently, you should always use a while loop with !feof() and fgets() if your read operation takes place after any new content has been written to a file. The changes to a file with read and write operations are saved only when you call fclose() or when the script comes to an end. Although PHP saves the file if you forget to use fclose(), you should always close the file explicitly. Don�t get into bad habits; one day they may cause your code to break and lose valuable data. When you create or open a file in a text editor, you can use your mouse to highlight and delete existing content, or position the insertion point exactly where you want. You don�t have that luxury with a PHP script, so you need to give it precise instructions. On the other hand, you don�t need to be there when the script runs. Once you have designed it, it runs automatically every time. Exploring the file system PHP�s file system functions can also open directories (folders) and inspect their contents. You put one of these functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existing filenames in the images folder and looping through the array to create a unique name for an uploaded file. From the web developer�s point of view, other practical uses of the file system functions are building dropdown menus displaying the contents of a folder and creating a script that prompts a user to download a file, such as an image or PDF document. Inspecting a folder with scandir() Let�s take a closer look at the scandir() function, which you used in PHP Solution 6-5. It returns an array consisting of the files and folders within a specified folder. Just pass the pathname of the folder (directory) as a string to scandir(), and store the result in a variable like this: $files = scandir('../images'); CHAPTER 7 196 You can examine the result by using print_r() to display the contents of the array, as shown in the following screenshot (the code is in scandir.php in the ch07 folder): As you can see, the array returned by scandir() doesn�t contain only files. The first two items are known as dot files, which represent the current and parent folders. The third item is a folder called _notes, and the penultimate item is a folder called thumbs. The array contains only the names of each item. If you want more information about the contents of a folder, it�s better to use the DirectoryIterator class. Inspecting the contents of a folder with DirectoryIterator The DirectoryIterator class is part of the Standard PHP Library (SPL), which has been part of PHP since PHP 5.0. The SPL offers a mind-boggling assortment of specialized iterators that allow you to create sophisticated loops with very little code. As the name suggests, the DirectoryIterator class lets you loop through the contents of a directory or folder. Because it�s a class, you instantiate a DirectoryIterator object with the new keyword and pass the path of the folder you want to inspect to the constructor like this: $files = new DirectoryIterator('../images'); Unlike scandir(), this doesn�t return an array of filenames—although you can loop through $files in the same way as an array. Instead, it returns an SplFileInfo object that gives you access to a lot more
  • 38. information about the folder�s contents than just the filenames. Because it�s an object, you can�t use print_r() to display its contents. However, if all you want to do is to display the filenames, you can use a foreach loop like this (the code is in iterator_01.php in the ch07 folder): $files = new DirectoryIterator('../images'); foreach ($files as $file) { echo $file . '<br>'; } USING PHP TO MANAGE FILES 197 This produces the following result: Although using echo in the foreach loop displays the filenames, the value stored in $file each time the loop runs is not a string. In fact, it�s another SplFileInfo object. Table 7-4 lists the main SplFileInfo methods that can be used to extract useful information about files and folders. Table 7-4. File information accessible through SplFileInfo methods Method Returns getFilename() The name of the file getPath() The current object�s relative path minus the filename, or minus the folder name if the current object is a folder getPathName() The current object�s relative path, including the filename or folder name, depending on the current type getRealPath() The current object�s full path, including filename if appropriate getSize() The size of the file or folder in bytes isDir() True, if the current object is a folder (directory) isFile() True, if the current object is a file isReadable() True, if the current object is readable isWritable() True, if the current object is writable CHAPTER 7 198 The RecursiveDirectoryIterator class burrows down into subfolders. To use it, you wrap it in the curiously named RecursiveIteratorIterator like this (the code is in iterator_03.php): $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('../images')); foreach ($files as $file) { echo $file->getRealPath() . '<br>'; } As the following screenshot shows, the RecursiveDirectoryIterator inspects the contents of all subfolders, revealing the contents of the thumbs and _notes folders, in a single operation: However, what if you want to find only certain types of files? Cue another iterator. . . . Restricting file types with the RegexIterator The RegexIterator acts as a wrapper to another iterator, filtering its contents using a regular expression (regex) as a search pattern. To restrict the search to the most commonly used types of image files, you need to look for any of the following filename extensions: .jpg, .png, or .gif. The regex used to search for these filename extensions looks like this: '/.(?:jpg|png|gif)$/i' In spite of its similarity to Vogon poetry, this regex matches image filename extensions in a caseinsensitive manner. The code in iterator_04.php has been modified like this: $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('../images')); $images = new RegexIterator($files, '/.(?:jpg|png|gif)$/i'); foreach ($images as $file) { echo $file->getRealPath() . '<br>'; } USING PHP TO MANAGE FILES 199 The original $files object is passed to the RegexIterator constructor, with the regex as the second argument, and the filtered set is stored in $images. The result is this: Only image files are now listed. The folders and other miscellaneous files have been removed. Before PHP 5, the same result would have involved many more lines of complex looping. To learn more about the mysteries of regular expressions (regexes), see my two-part tutorial at www.adobe.com/devnet/dreamweaver/articles/regular_expressions_pt1.html. As you progress through this book, you�ll see I make frequent use of regexes. They�re a useful tool to add to your skill set.
  • 39. I expect that by this stage, you might be wondering if this can be put to any practical use. OK, let�s build a drop-down menu of images in a folder. PHP Solution 7-3: Building a drop-down menu of files When you work with a database, you often need a list of images or other files in a particular folder. For instance, you may want to associate a photo with a product detail page. Although you can type the name of the image into a text field, you need to make sure that the image is there and that you spell its name correctly. Get PHP to do the hard work by building a drop-down menu automatically. It�s always up-todate, and there�s no danger of misspelling the name. 1. Create a PHP page called imagelist.php in the filesystem folder. Alternatively, use imagelist_01.php in the ch07 folder. CHAPTER 7 200 2. Create a form inside imagelist.php, and insert a <select> element with just one <option> like this (the code is already in imagelist_01.php): <form id="form1" name="form1" method="post" action=""> <select name="pix" id="pix"> <option value="">Select an image</option> </select> </form> This <option> is the only static element in the drop-down menu. 3. Amend the form like this: <form id="form1" name="form1" method="post" action=""> <select name="pix" id="pix"> <option value="">Select an image</option> <?php $files = new DirectoryIterator('../images'); $images = new RegexIterator($files, '/.(?:jpg|png|gif)$/i'); foreach ($images as $image) { ?> <option value="<?php echo $image; ?>"><?php echo $image; ?></option> <?php } ?> </select> </form> 4. Make sure that the path to the images folder is correct for your site�s folder structure. 5. Save imagelist.php, and load it into a browser. You should see a drop-down menu listing all the images in your images folder, as shown in Figure 7-1. USING PHP TO MANAGE FILES 201 Figure 7-1. PHP makes light work of creating a drop-down menu of images in a specific folder. When incorporated into an online form, the filename of the selected image appears in the $_POST array identified by the name attribute of the <select> element—in this case, $_POST['pix']. That�s all there is to it! You can compare your code with imagelist_02.php in the ch07 folder. PHP Solution 7-4: Creating a generic file selector The previous PHP solution relies on an understanding of regular expressions. Adapting it to work with other filename extensions isn�t difficult, but you need to be careful that you don�t accidentally delete a vital character. Unless regexes or Vogon poetry are your specialty, it�s probably easier to wrap the code in a function that can be used to inspect a specific folder and create an array of filenames of specific types. For example, you might want to create an array of PDF document filenames or one that contains both PDFs and Word documents. Here�s how you do it. 1. Create a new file called buildlist.php in the filesystem folder. The file will contain only PHP code, so delete any HTML inserted by your editing program. CHAPTER 7 202 2. Add the following code to the file: function buildFileList($dir, $extensions) { if (!is_dir($dir) || !is_readable($dir)) { return false; } else { if (is_array($extensions)) { $extensions = implode('|', $extensions);
  • 40. } } } This defines a function called buildFileList(), which takes two arguments: $dir: The path to the folder from which you want to get the list of filenames. $extensions: This can be either a string containing a single filename extension or an array of filename extensions. To keep the code simple, the filename extensions should not include a leading period. The function begins by checking whether $dir is a folder and is readable. If it isn�t, the function returns false, and no more code is executed. If $dir is OK, the else block is executed. It also begins with a conditional statement that checks whether $extensions is an array. If it is, it�s passed to implode(), which joins the array elements with a vertical pipe (|) between each one. A vertical pipe is used in regexes to indicate alternative values. Let�s say the following array is passed to the function as the second argument: array('jpg', 'png', 'gif') The conditional statement converts it to jpg|png|gif. So, this looks for jpg, or png, or gif. However, if the argument is a string, it remains untouched. 3. You can now build the regex search pattern and pass both arguments to the DirectoryIterator and RegexIterator like this: function buildFileList($dir, $extensions) { if (!is_dir($dir) || !is_readable($dir)) { return false; } else { if (is_array($extensions)) { $extensions = implode('|', $extensions); } $pattern = "/.(?:{$extensions})$/i"; $folder = new DirectoryIterator($dir); $files = new RegexIterator($folder, $pattern); } } USING PHP TO MANAGE FILES 203 The regex pattern is built using a string in double quotes and wrapping $extensions in curly braces to make sure it�s interpreted correctly by the PHP engine. Take care when copying the code. It�s not exactly easy to read. 4. The final section of the code extracts the filenames to build an array, which is sorted and then returned. The finished function definition looks like this: function buildFileList($dir, $extensions) { if (!is_dir($dir) || !is_readable($dir)) { return false; } else { if (is_array($extensions)) { $extensions = implode('|', $extensions); } // build the regex and get the files $pattern = "/.(?:{$extensions})$/i"; $folder = new DirectoryIterator($dir); $files = new RegexIterator($folder, $pattern); // initialize an array and fill it with the filenames $filenames = array(); foreach ($files as $file) { $filenames[] = $file->getFilename(); } // sort the array and return it natcasesort($filenames); return $filenames; } } This initializes an array and uses a foreach loop to assign the filenames to it with the getFilename() method. Finally, the array is passed to natcasesort(), which sorts it in a
  • 41. natural, case-insensitive order. What ―natural‖ means is that strings that contain numbers are sorted in the same way as a person would. For example, a computer normally sorts img12.jpg before img2.jpg, because the 1 in 12 is lower than 2. Using natcasesort() results in img2.jpg preceding img12.jpg. 5. To use the function, use as arguments the path to the folder and the filename extensions of the files you want to find. For example, you could get all Word and PDF documents from a folder like this: $docs = buildFileList('folder_name', array('doc', 'docx', 'pdf')); The code for the buildFileList() function is in buildlist.php in the ch07 folder. Accessing remote files Reading, writing, and inspecting files on your local computer or on your own website is useful. But allow_url_fopen also gives you access to publicly available documents anywhere on the Internet. You can�t directly include files from other servers—not unless allow_url_include is on—but you can read CHAPTER 7 204 the content, save it to a variable, and manipulate it with PHP functions before incorporating it in your own pages or saving the information to a database. You can also write to documents on a remote server as long as the owner sets the appropriate permissions. A word of caution is in order here. When extracting material from remote sources for inclusion in your own pages, there�s a potential security risk. For example, a remote page might contain malicious scripts embedded in <script> tags or hyperlinks. Unless the remote page supplies data in a known format from a trusted source—such as product details from the Amazon.com database, weather information from a government meteorological office, or a newsfeed from a newspaper or broadcaster—sanitize the content by passing it to htmlentities() (see PHP Solution 5-3). As well as converting double quotes to &quot;, htmlentities() converts < to &lt; and > to &gt;. This displays tags in plain text, rather than treating them as HTML. If you want to permit some HTML tags, use the strip_tags() function instead. If you pass a string to strip_tags(), it returns the string with all HTML tags and comments stripped out. It also removes PHP tags. A second, optional argument is a list of tags that you want preserved. For example, the following strips out all tags except paragraphs and first- and second-level headings: $stripped = strip_tags($original, '<p><h1><h2>'); For an in-depth discussion of security issues, see Pro PHP Security by Chris Snyder and Michael Southwell (Apress, 2005, ISBN: 978-1-59059-508-4). Consuming news and other RSS feeds Some of the most useful remote sources of information that you might want to incorporate in your sites come from RSS feeds. RSS stands for Really Simple Syndication, and it�s a dialect of XML. XML is similar to HTML in that it uses tags to mark up content. Instead of defining paragraphs, headings, and images, XML tags are used to organize data in a predictable hierarchy. XML is written in plain text, so it�s frequently used to share information between computers that might be running on different operating systems. Figure 7-2 shows the typical structure of an RSS 2.0 feed. The whole document is wrapped in a pair of <rss> tags. This is the root element, similar to the <html> tags of a web page. The rest of the document is wrapped in a pair of <channel> tags, which always contains the following three elements that describe the RSS feed: <title>, <description>, and <link>. USING PHP TO MANAGE FILES 205 Figure 7-2. The main contents of an RSS feed are in the item elements. In addition to the three required elements, the <channel> can contain many other elements, but the interesting material is to be found in the <item> elements. In the case of a news feed, this is where the individual news items can be found. If you�re looking at the RSS feed from a blog, the <item> elements normally contain summaries of the blog posts. Each <item> element can contain several elements, but those shown in Figure 7-2 are the most common—and usually the most interesting:
  • 42. <title>: The title of the item <link>: The URL of the item <pubDate>: Date of publication <description>: Summary of the item This predictable format makes it easy to extract the information from an RSS feed using SimpleXML. You can find the full RSS Specification at www.rssboard.org/rss-specification. Unlike most technical specifications, it�s written in plain language, and easy to read. Using SimpleXML As long as you know the structure of an XML document, SimpleXML does what it says on the tin: it makes extracting information from XML simple. The first step is to pass the URL of the XML document to simplexml_load_file(). You can also load a local XML file by passing the path as an argument. For example, this gets the world news feed from the BBC: $feed = simplexml_load_file('http://feeds.bbci.co.uk/news/world/rss.xml'); This creates an instance of the SimpleXMLElement class. All the elements in the feed can now be accessed as properties of the $feed object, using the names of the elements. With an RSS feed, the <item> elements can be accessed as $feed->channel->item. CHAPTER 7 206 To display the <title> of each <item>, create a foreach loop like this: foreach ($feed->channel->item as $item) { echo $item->title . '<br>'; } If you compare this with Figure 7-2, you can see that you access elements by chaining the element names with the -> operator until you reach the target. Since there are multiple <item> elements, you need to use a loop to tunnel further down. Alternatively, use array notation like this: $feed->channel->item[2]->title This gets the <title> of the third <item> element. Unless you want only a specific value, it�s simpler to use a loop. With that background out of the way, let�s use SimpleXML to display the contents of a news feed. PHP Solution 7-5: Consuming an RSS news feed This PHP solution shows how to extract the information from a live news feed using SimpleXML and display it in a web page. It also shows how to format the <pubDate> element to a more user-friendly format and how to limit the number of items displayed using the LimitIterator class. 1. Create a new page called newsfeed.php in the filesystem folder. This page will contain a mixture of PHP and HTML. 2. The news feed chosen for this PHP solution is the BBC World News. A condition of using most news feeds is that you acknowledge the source. So add The Latest from BBC News formatted as an <h1> heading at the top of the page. See http://news.bbc.co.uk/1/hi/help/rss/4498287.stm for the full terms and conditions of using a BBC news feed on your own site. 3. Create a PHP block below the heading, and add the following code to load the feed: $url = 'http://feeds.bbci.co.uk/news/world/rss.xml'; $feed = simplexml_load_file($url); 4. Use a foreach loop to access the <item> elements and display the <title> of each one: foreach ($feed->channel->item as $item) { echo $item->title . '<br>'; } 5. Save newsfeed.php, and load the page in a browser. You should see a long list of news items similar to Figure 7-3. USING PHP TO MANAGE FILES 207 Figure 7-3. The news feed contains a large number of items. 6. The normal feed often contains 50 or more items. That�s fine for a news site, but you probably want a shorter selection in your own site. Use another SPL iterator to select a specific range of items. Amend the code like this: $url = 'http://feeds.bbci.co.uk/news/world/rss.xml'; $feed = simplexml_load_file($url, 'SimpleXMLIterator'); $filtered = new LimitIterator($feed->channel->item, 0 , 4); foreach ($filtered as $item) { echo $item->title . '<br>';
  • 43. } To use SimpleXML with an SPL iterator, you need to supply the name of the SimpleXMLIterator class as the second argument to simplexml_load_file(). You can then pass the SimpleXML element you want to affect to an iterator constructor. In this case, $feed->channel->item is passed to the LimitIterator constructor. The LimitIterator takes three arguments: the object you want to limit, the starting point (counting from 0), and the number of times you want the loop to run. This code starts at the first item and limits the number of items to four. The foreach loop now loops over the $filtered result. If you test the page again, you�ll see just four titles, as shown in Figure 7-4. Don�t be surprised if the selection of headlines is different from before. The BBC News website is updated every minute. CHAPTER 7 208 Figure 7-4. The LimitIterator restricts the number of items displayed. 7. Now that you have limited the number of items, amend the foreach loop to wrap the <title> elements in a link to the original article, and display the <pubDate> and <description> items. The loop looks like this: foreach ($filtered as $item) { ?> <h2><a href="<?php echo $item->link; ?>"><?php echo $item->title; � ?></a></h2> <p class="datetime"><?php echo $item->pubDate; ?></p> <p><?php echo $item->description; ?></p> <?php } ?> 8. Save the page, and test it again. The links take you directly to the relevant news story on the BBC website. The news feed is now functional, but the <pubDate> format follows the format laid down in the RSS specification, as shown in the next screenshot: 9. To format the date and time in a more user-friendly way, pass $item->pubDate to the DateTime class constructor, and then use the DateTime format() method to display it. 10. Change the code in the foreach loop like this: <p class="datetime"><?php $date= new DateTime($item->pubDate); echo $date->format('M j, Y, g:ia'); ?></p> This reformats the date like this: The mysterious PHP formatting strings for dates are explained in Chapter 14. USING PHP TO MANAGE FILES 209 11. That looks a lot better, but the time is still expressed in GMT (London time). If most of your site�s visitors live on the East Coast of the United States, you probably want to show the local time. That�s no problem with a DateTime object. Use the setTimezone() method to change to New York time. You can even automate the display of EDT (Eastern Daylight Time) or EST (Eastern Standard Time) depending on whether daylight saving time is in operation. Amend the code like this: <p class="datetime"><?php $date = new DateTime($item->pubDate); $date->setTimezone(new DateTimeZone('America/New_York')); $offset = $date->getOffset(); $timezone = ($offset == -14400) ? ' EDT' : ' EST'; echo $date->format('M j, Y, g:ia') . $timezone; ?></p> To create a DateTimeZone object, you pass it as an argument one of the time zones listed at http://docs.php.net/manual/en/timezones.php. This is the only place that the DateTimeZone object is needed, so it has been created directly as the argument to the setTimezone() method. There isn�t a dedicated method that tells you whether daylight saving time is in operation, but the getOffset() method returns the number of seconds the time is offset from Coordinated Universal Time (UTC). The following line determines whether to display EDT or EST: $timezone = ($offset == -14400) ? ' EDT' : ' EST'; This uses the value of $offset with the conditional operator. In summer, New York is 4 hours behind UTC (–14440 seconds). So, if $offset is –14400, the condition equates to true, and EDT is assigned to $timezone. Otherwise, EST is used. Finally, the value of $timezone is concatenated to the formatted time. The string used for $timezone has a leading space to separate the time zone from the time. When the page is loaded, the time is adjusted to the East Coast of the United States like this: 12. All the page needs now is smartening up with CSS. Figure 7-5 shows the finished news feed styled with newsfeed.css in the styles folder.
  • 44. You can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friends of ED, 2008, ISBN: 978-1-4302-1011-5). CHAPTER 7 210 Figure 7-5. The live news feed requires only a dozen lines of PHP code. Although I have used the BBC News feed for this PHP solution, it should work with any RSS 2.0 feed. For example, you can try it locally with http://rss.cnn.com/rss/edition.rss. Using a CNN news feed in a public website requires permission from CNN. Always check with the copyright holder for terms and conditions before incorporating a feed into a website. Creating a download link A question that crops up regularly in online forums is, ―How do I create a link to an image (or PDF file) that prompts the user to download it?‖ The quick solution is to convert the file into a compressed format, such as ZIP. This frequently results in a smaller download, but the downside is that inexperienced users may not know how to unzip the file, or they may be using an older operating system that doesn�t include an extraction facility. With PHP file system functions, it�s easy to create a link that automatically prompts the user to download a file in its original format. PHP Solution 7-6: Prompting a user to download an image The script in this PHP solution sends the necessary HTTP headers, opens the file, and outputs its contents as a binary stream. 1. Create a PHP file called download.php in the filesystem folder. The full listing is given in the next step. You can also find it in download.php in the ch07 folder. USING PHP TO MANAGE FILES 211 2. Remove any default code created by your script editor, and insert the following code: <?php // define error page $error = 'http://localhost/phpsols/error.php'; // define the path to the download folder $filepath = 'C:/xampp/htdocs/phpsols/images/'; $getfile = NULL; // block any attempt to explore the filesystem if (isset($_GET['file']) && basename($_GET['file']) == $_GET['file']) { $getfile = $_GET['file']; } else { header("Location: $error"); exit; } if ($getfile) { $path = $filepath . $getfile; // check that it exists and is readable if (file_exists($path) && is_readable($path)) { // get the file's size and send the appropriate headers $size = filesize($path); header('Content-Type: application/octet-stream'); header('Content-Length: '. $size); header('Content-Disposition: attachment; filename=' . $getfile); header('Content-Transfer-Encoding: binary'); // open the file in read-only mode // suppress error messages if the file can't be opened $file = @fopen($path, 'r'); if ($file) { // stream the file and exit the script when complete fpassthru($file); exit; } else { header("Location: $error"); } } else { header("Location: $error"); } The only two lines that you need to change in this script are highlighted in bold type. The first
  • 45. defines $error, a variable that contains the URL of your error page. The second line that needs to be changed defines the path to the folder where the download file is stored. The script works by taking the name of the file to be downloaded from a query string appended to the URL and saving it as $getfile. Because query strings can be easily tampered with, CHAPTER 7 212 $getfile is initially set to NULL. This is an important security measure. If you fail to do this, you could give a malicious user access to any file on your server. The opening conditional statement uses basename() to make sure that an attacker cannot request a file, such as one that stores passwords, from another part of your file structure. As explained in Chapter 4, basename() extracts the filename component of a path, so if basename($_GET['file']) is different from $_GET['file'], you know there�s an attempt to probe your server, and you can stop the script from going any further by using the header() function to redirect the user to the error page. After checking that the requested file exists and is readable, the script gets the file�s size, sends the appropriate HTTP headers, and opens the file in read-only mode using fopen(). Finally, fpassthru() dumps the file to the output buffer. But if the file can�t be opened or doesn�t exist, the user is redirected to the error page. 3. Test the script by creating another page and add a couple of links to download.php. Add a query string at the end of each link with file= followed by the name a file to be downloaded. You�ll find a page called getdownloads.php in the ch07 folder, which contains the following two links: <p><a href="download.php?file=maiko.jpg">Download image 1</a></p> <p><a href="download.php?file=basin.jpg">Download image 2</a></p> 4. Click one of the links, and the browser should present you with a dialog box prompting you to download the file or choose a program to open it, as shown in Figure 7-6. Figure 7-6. The browser prompts the user to download the image, rather than opening it directly. USING PHP TO MANAGE FILES 213 5. Select Save File, and click OK, and the file should be saved rather than displayed. Click Cancel to abandon the download. Whichever button you click, the original page remains in the browser window. The only time download.php should load into the browser is if the file cannot be opened. That�s why it�s important to send the user to an error page if there�s a problem. I�ve demonstrated download.php with image files, but it can be used for any type of file because the headers send the file as a binary stream. This script relies on header() to send the appropriate HTTP headers to the browser. It is vital to ensure that there are no new lines or whitespace ahead of the opening PHP tag. If you have removed all whitespace and still get an error message saying “headers already sent,” your editor may have inserted invisible control characters at the beginning of the file. Some editing programs insert the byte order mark (BOM), which is known to cause problems with the ability to use the header() function. Check your program preferences to make sure the option to insert the BOM is deselected. Chapter review The file system functions aren�t particularly difficult to use, but there are many subtleties that can turn a seemingly simple task into a complicated one. It�s important to check that you have the right permissions. Even when handling files in your own website, PHP needs permission to access any folder where you want to read files or write to them. The SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine the contents of folders. Used in combination with the SplFileInfo methods and the RegexIterator, you can quickly find files of a specific type within a folder or folder hierarchy. When dealing with remote data sources, you need to check that allow_url_fopen hasn�t been disabled. One of the most common uses of remote data sources is extracting information from RSS news feeds or XML documents, a task that takes only a few lines of code thanks to SimpleXML. In the next two chapters, we�ll put some of the PHP solutions from this chapter to further practical use when working with images and building a simple user authentication system. CHAPTER 7 214 __