Login

File And Directory Manipulation In PHP (part 2)

Now that you know the basics of reading and writing files, this second segment of our tutorial on the PHP filesystem API takes you into deeper waters, showing you how to copy, delete and rename files; scan directories; work with uploaded files over HTTP; perform pattern matches on file names; and read and write to processes instead of files.

In the first part of this article, I introduced you to the basic filesystem
functions available in PHP, showing you (among other things) how to read and
write files on the file system and obtain detailed status information on any
file, including data on attributes like file sizes and permissions.

That, however, was just the tip of the iceberg. PHP’s filesystem API includes
a number of specialized file and directory functions, which let you copy, delete
and rename files; scan directories; work with uploaded files over HTTP; perform
pattern matches on file names; and read and write to processes instead of files.
So keep reading – we’ve got a long and interesting journey ahead of us!

{mospagebreak title=Stripping It To The Bone}

You may remember, from the first part of this article, how I used
thefgets() function to read the contents of a file and print it to the
browser. In case you don’t, here’s a quick reminder:

<?php

// set file to read
$filename = “mindspace.txt”;

// open file
$fh = fopen ($filename, “r”) or die(“Could not open file”);

// read file
while (!feof($fh))
{
$data = fgets($fh);
echo $data;
}

// close file
fclose ($fh);

echo “– ALL DONE –“;

?>

PHP also offers the fgetss() function, which works just like the
regularfgets() function, except that it also strips out HTML and PHP code
from the lines it reads. So, for example, if you had a file containing
intermingled HTML code and ASCII text (as most HTML files are), like in the
following example,

In addition to the simple fgets() function, PHP also offers the
more-sophisticated fgetcsv() function, which not only reads in data from a file,
but also parses each line and, using the comma (,) symbol as delimiter, splits
the data on each line into fields for further processing. The return value from
every call to fgetcsv() is an array containing the fields found.

In this case, the comma-separated values in the input file are automatically
parsed into an array, and can then be processed, or reassembled in any order you
like, to create different output. Here’s what the script above results in:

If what you’re really after involves reading configuration variables in from
a standard .INI file, you don’t need to write custom code to parse the file and
read in the variable-value pairs. Instead, just use PHP’sparse_ini_file()
function, which automatically takes care of this for you.

The only problem with the approach, however, is that variables with the same
name from different sections will override each other; if there are multiple
configuration variables with the same name, the output array will always contain
only the last value. In order to illustrate, look what happens when I add a new
section to the sample file above which repeats some of the variables from a
previous section:

As you can see, some of the variable-value pairs (from the “temp” section of
the file) have been lost. PHP offers a solution to this problem by allowing a
second, optional argument to parse_ini_file() – a Boolean indicating whether the
namespaces of the various sections should be respected. When I add that to the
script above,

With the addition of the second argument to parse_ini_file(), PHP now creates
a nested array, with the outer array referencing the sections, and each inner
one referencing the variables in each section.

{mospagebreak title=The Right Path}

In addition to functions that allow you to obtain information on file sizes,
permissions and modification times, PHP also offers a number of functions
designed to manipulate file and path names, and split a file path into its
constituent components. The two most commonly-used ones here are the basename()
function, which returns the filename component of a path, and the dirname()
function, which returns the directory name component of a path

The following example demonstrates the basename() and dirname() components in
action, by splitting a file path into its constituents:

<?php

// set path
$path = “/usr/local/apache/bin/httpd”;

// print filename
echo “File is ” . basename($path) . “rn”;

// print directory name
echo “Directory is ” . dirname($path) . “rn”;

?>

Here’s the output:

File is httpdDirectory is /usr/local/apache/bin

You can also use the pathinfo() function to obtain this information – this
function returns an associative array containing keys for directory name, file
name and file extension. Take a look at this next script, which returns this
information for the directory holding the currently executing script.

<?php

// parse current file path
$info = pathinfo($_SERVER[‘PHP_SELF’]);

// print info
print_r($info);

?>

Here’s the output:

Array([dirname] => /dev/php[basename] =>
fs.php[extension] => php)

Finally, you can use the realpath() function to translate relative paths into
absolute ones, as below:

You can obtain the script’s current working directory by combining
thedirname() function with the special $_SERVER[‘SCRIPT_FILENAME’]
variable,

<?php

echo “Current directory is ” . dirname($_SERVER[‘SCRIPT_FILENAME’]);

?>

or with the alternative getcwd() function, as below,

<?php

// get current working directory
echo “Current directory is ” . getcwd();

?>

or even through creative use of the realpath() function, as below:

<?php

echo “Current directory is ” . realpath(“.”);

?>

{mospagebreak title=Move It}

In addition to offering you path information, PHP comes with a whole bunch of
functions designed to simplify the task of moving, copying, renaming and
deleting files on the filesystem. The first of these is the copy() function,
which accepts two arguments, a source file and a destination, and copies the
former to the latter. Here’s an example which demonstrates:

<?php

// check to see if file exists
// if so, back it up
if (file_exists(“matrix.txt”))
{
copy (“matrix.txt”, “matrix.txt.orig”) or die (“Could
not copy file”); }

?>

Note that if the destination file already exists, copy() will usually
overwrite it. A failure to copy the file will cause copy() to return false;
success returns true.

A corollary to the copy() function is the rename() function, which can be
used to both rename and move files. Like copy(), it too accepts two arguments, a
source file and a destination file. Consider the following example, which
renames a file,

<?php

// check to see if file exists
// if so, rename it
if (file_exists(“matrix.txt”))
{
rename (“matrix.txt”, “neoworld.txt”) or die (“Could
not rename file”); }

?>

and this one, which simultaneously renames and moves a file.

<?php

// check to see if file exists
// if so, move and rename it
if (file_exists(“/home/john/newsletters”))
{
rename (“/home/john/newsletters”, “/home/john/mail/lists”)
or die (“Could not move file”); }

?>

It’s possible to rename directories in the same manner as files – as
illustrated in this next snippet:

<?php

// check to see if directory exists
// if so, rename it
if (is_dir(“Mail”))
{
rename (“Mail”, “mail-jun-03”) or die (“Could not rename
directory”); }

?>

The rename() function comes in handy when you need to update files which are
constantly being used by multiple processes. Instead of directly updating the
target file with new data, rename() allows you to copy the original file
contents into a new file, make your changes and then, once you’re happy with the
result, simply rename() the new file to the old one.

Note my use of the tempnam() function above – this function generates a
unique file name, given a directory and a filename prefix, and can help to avoid
filename collisions between different processes.

When it comes time to delete files, PHP offers the unlink() function, which
can be used to erase a file from the filesystem. Consider the following example,
which demonstrates by deleting a specific file:

<?php

// check to see if file exists
// if so, erase it
if (file_exists(“error.log”))
{
unlink (“error.log”) or die (“Could not delete file”);
}

?>

You can also use the unlink() function to iterate over a directory and remove
all the files within it – this is demonstrated in an example coming up
shortly.

Note that the unlink() function (and indeed, all other file
manipulationfunctions) will fail if the user ID under which the Web server
is running does not have adequate permissions to delete or otherwise modify the
named file(s).

{mospagebreak title=Beam Me Up}

Most often, you’ll find yourself using rename(), copy() and unlink()
functions in the context of files uploaded to PHP via a Web browser – so-called
“HTTP file uploads”. Consider the following example, whichdemonstrates:

In this case, when a file is uploaded, it is automatically stored in a
temporary directory by PHP, and its temporary filename is exposed via the
“tmp_name” key of the $_FILES array. A copy() function can then be used to copy
the uploaded file from its temporary location to its new location, and it can
also be renamed along the way if needed (as in the above example). Once the
script finishes executing, the temporary file is automatically deleted by
PHP.

Since file uploads are fairly common in PHP, the language also offers two
specialized functions designed specifically to assist you in the process of
handling such uploaded files: the is_uploaded_file() function, which tests if a
file was uploaded via the HTTP POST method, and themove_uploaded_file()
function, which is used to move an uploaded file to a new location after
verifying its integrity. Here is a rewrite of the previous example using these
functions, in order to better illustrate how they can be used:

Thus far, most of the examples you’ve seen have dealt with individual files.
However, you often find yourself faced with the task of iterating over one or
more directories and processing the file list within each. In order to meet this
requirement, PHP offers a comprehensive set of directory manipulation functions,
which allow developers to read and parse an entire directory listing.

In order to demonstrate, consider the following simple example, which lists
all the files in the directory “/bin”:

You can combine the script above with the getcwd() function discussed earlier
to obtain a list of all the files in the current working directory at any time –
I’ll leave that to you to try out for yourself.

{mospagebreak title=A Pattern Emerges}

Want to list just the files matching a specific pattern? Use the neat little
fnmatch() function, new in PHP 4.3, which matches strings against wildcard
patterns. Here’s a quick example:

The * wildcard matches one or more characters; if this is not what you want,
you can also use the ? wildcard to match a single character.

An alternative to using fnmatch() with the opendir() and readdir() functions
is the glob() function, also new to PHP 4.3 – this function searches the current
directory for files matching the specified pattern and returns them as an array.
Consider the following rewrite of the example above, which demonstrates:

Since glob() looks in the current directory for files, remember to
alwayschdir() to the correct directory before executing
glob().

{mospagebreak title=Purging The Dead}

Not only does PHP let you read directory contents, it also allows you to
create and delete directories with the mkdir() and rmdir() functions
respectively. Consider the following example, which creates a directory,

<?php

// create directory
mkdir(“/tmp/stuff”) or die (“Could not make directory”);

As with the other filesystem manipulation functions, these functions too will
fail if the user the Web server is running as lacks sufficient privileges to
create and delete directories on the disk. Directories created with mkdir() will
be owned by the process running the Web server.

It’s interesting also to note that the rmdir() function operates only if the
directory to be deleted is completely empty. Try running it on a directory
containing existing files, as below,

<?php

// create directory
mkdir(“/tmp/stuff”) or die (“Could not make directory”);

// create sub-directory
mkdir(“/tmp/stuff/personal”) or die (“Could not make directory”);

Warning: mkdir(): File exists in /home/web/rmdir.php on line 4 Could
not remove directory

Since it’s unlikely that you’ll find empty directories just waiting for your
rmdir() in the real world, you’ll normally need to empty the directory manually
prior to calling rmdir() on it. The following example demonstrates, by combining
the unlink() function discussed previously with some recursive logic to create a
function designed specifically to erase the contents of a directory (and its
sub-directories) in one fell swoop:

If you’re looking for information on the total size of a partition or mount
point, PHP offers the relatively-new disk_total_space() anddisk_free_space()
functions, which return the total available space and total free space, in
bytes, on a particular partition. Consider the following example, which
demonstrates:

Just as PHP offers the fopen() and fclose() functions to open and close file
handles, there’s also the popen() and pclose() functions, which can be used to
open uni-directional handles to processes. Once a process handle has been
created, data can be read from it or written to it using the standard fgets(),
fputs(), fread() and fwrite() file functions.

Consider the following example, which demonstrates by opening a pipe to the
“cat” command:

<?php

// open handle to process
$ph = popen(“/bin/cat /etc/passwd”, “r”);

// read process output into variable
$contents = fread($ph, 2096);

// display contents
echo $contents;

// close process
pclose($ph);

?>

As you can see, opening a pipe to a process and reading from it is very
similar to opening and reading a file. As with files, the first step is to
obtain a handle to the process with popen() – this handle serves as the
foundation for all future communication. Once a handle has been obtained, data
can be read from, or written to, the handle using the file input/output
functions you’re already familiar with. The handle can be closed at any time
with the pclose() function.

If you need bi-directional communication, PHP 4.3 also offers the
newproc_open() and proc_close() functions, which offers a greater degree of
control over process communication.

{mospagebreak title=Disk Full}

And that’s about all I have. Over the course of the last few pages, I took
you ever deeper into the waters of PHP’s filesystem API, demonstrating a number
of its more arcane features and functions. I showed you how to strip out program
code from a file while reading it, how to parse comma-separated data from a file
into PHP structures, and how to read configuration variables into a PHP
associative array.

Next, I introduced you to PHP’s file copy, move and delete functions, and
demonstrated them in the context of a file upload application. I also showed you
how to read and display the contents of a directory, and how to recursively
iterate through a series of nested directories. Finally, I wrapped things up
with a brief look at how to obtain disk usage reports for a mount point or
partition, and explained how you could read data from processes just as you do
with files.

While this tutorial did cover many of the common uses of PHP’s file and
directory manipulation functions, it is by no means exhaustive. There are many
more things you can do with PHP’s file functions – and you can get some great
ideas (and learn a number of interesting things as well) by perusing the PHP
manual pages for these functions, at the links below: